# Storage

Storage APIs have been standardized with consistent pagination and naming patterns across all methods.

## Database migration

Run these SQL migrations through your normal migration process (e.g., Prisma Migrate, Drizzle Kit, or your DBA review process).

### Scorers table column rename

The `runtimeContext` column in `mastra_scorers` was renamed to `requestContext`.

> **Who needs this migration:** Only if you use `@mastra/pg` or `@mastra/libsql` with evals/scoring and have existing data in the `runtimeContext` column.

> **What breaks without it:** Existing score records won't have their request context data accessible.

After deploying v1 (which adds the new `requestContext` column on init), copy the data and drop the old column:

```sql
UPDATE mastra_scorers SET "requestContext" = "runtimeContext" WHERE "runtimeContext" IS NOT NULL;
ALTER TABLE mastra_scorers DROP COLUMN "runtimeContext";
```

### Duplicate spans migration

If you're upgrading from an older version of Mastra, you may have duplicate `(traceId, spanId)` entries in your `mastra_spans` table. V1 adds a unique constraint on these columns to ensure data integrity, but this constraint can't be added if duplicates exist.

> **Who needs this migration:** Only if you have existing spans data from Mastra versions prior to v1 and encounter errors about duplicate key violations or constraint creation failures.

> **What breaks without it:** The storage initialization may fail when trying to add the unique constraint, or you may see errors like "duplicate key value violates unique constraint".

#### Option 1: Use the CLI (Recommended)

Run the migration command which automatically deduplicates spans and adds the constraint:

```bash
npx mastra migrate
```

The CLI bundles your project, connects to your configured storage, and runs the migration. It keeps the most complete record (based on `endTime` and attributes) when removing duplicates.

#### Option 2: Manual SQL (PostgreSQL)

If you prefer to run the migration manually:

```sql
-- Remove duplicates, keeping the most complete record
DELETE FROM mastra_spans a USING mastra_spans b
WHERE a.ctid < b.ctid
  AND a."traceId" = b."traceId"
  AND a."spanId" = b."spanId";

-- Add the unique constraint
ALTER TABLE mastra_spans ADD CONSTRAINT mastra_spans_trace_span_unique UNIQUE ("traceId", "spanId");
```

#### Option 3: Manual migration (Other databases)

For ClickHouse, LibSQL, MongoDB, or MSSQL, use the programmatic API:

```typescript
const storage = mastra.getStorage()
const observabilityStore = await storage.getStore('observability')

// Check if migration is needed
const status = await observabilityStore?.checkSpansMigrationStatus()
console.log(status)

// Run the migration
const result = await observabilityStore?.migrateSpans()
console.log(result)
```

### JSON columns (TEXT → JSONB)

**PostgreSQL only.** The `metadata` column in `mastra_threads` and `snapshot` column in `mastra_workflow_snapshot` changed from TEXT to JSONB.

> **Recommended:** Migrating to JSONB enables native PostgreSQL JSON operators and GIN indexing for better query performance on JSON fields.

```sql
ALTER TABLE mastra_threads
ALTER COLUMN metadata TYPE jsonb
USING metadata::jsonb;

ALTER TABLE mastra_workflow_snapshot
ALTER COLUMN snapshot TYPE jsonb
USING snapshot::jsonb;
```

## Added

### Storage composition in `MastraCompositeStore`

`MastraCompositeStore` can now compose storage domains from different adapters. Use it when you need different databases for different purposes - for example, PostgreSQL for memory and workflows, but a specialized database for observability.

```typescript
import { MastraCompositeStore } from '@mastra/core/storage'
import { MemoryPG, WorkflowsPG, ScoresPG } from '@mastra/pg'
import { MemoryLibSQL } from '@mastra/libsql'
import { Mastra } from '@mastra/core'

// Compose domains from different stores
const mastra = new Mastra({
  storage: new MastraCompositeStore({
    id: 'composite',
    domains: {
      memory: new MemoryLibSQL({ url: 'file:./local.db' }),
      workflows: new WorkflowsPG({ connectionString: process.env.DATABASE_URL }),
      scores: new ScoresPG({ connectionString: process.env.DATABASE_URL }),
    },
  }),
})
```

See the [Storage Composition reference](https://mastra.ai/reference/storage/composite) for more details.

## Changed

### `MastraStorage` renamed to `MastraCompositeStore`

The `MastraStorage` class has been renamed to `MastraCompositeStore` to better reflect its role as a composite storage implementation that routes different domains to different underlying stores. This avoids confusion with the general "Mastra Storage" concept (the `storage` property on the Mastra instance).

The old `MastraStorage` name remains available as a deprecated alias for backward compatibility, but will be removed in a future version.

To migrate, update your imports and instantiation:

```diff
- import { MastraStorage } from "@mastra/core/storage";
+ import { MastraCompositeStore } from "@mastra/core/storage";
  import { MemoryLibSQL } from "@mastra/libsql";
  import { WorkflowsPG } from "@mastra/pg";

  export const mastra = new Mastra({
-   storage: new MastraStorage({
+   storage: new MastraCompositeStore({
      id: "composite",
      domains: {
        memory: new MemoryLibSQL({ url: "file:./memory.db" }),
        workflows: new WorkflowsPG({ connectionString: process.env.DATABASE_URL }),
      },
    }),
  });
```

> **Note:** If you're using a single store implementation (like `PostgresStore` or `LibSQLStore`) directly, no changes are needed. This only affects code that explicitly uses `MastraStorage` for composite storage.

### Required `id` property for storage instances

Storage instances now require an `id` property. This unique identifier is used for tracking and managing storage instances within Mastra. The `id` should be a descriptive, unique string for each storage instance in your application.

To migrate, add an `id` field to your storage constructor.

```diff
const storage = new PostgresStore({
+   id: 'main-postgres-store',
    connectionString: process.env.POSTGRES_CONNECTION_STRING,
    schemaName: 'public',
  });

  const upstashStore = new UpstashStore({
+   id: 'upstash-cache-store',
    url: process.env.UPSTASH_REDIS_REST_URL,
    token: process.env.UPSTASH_REDIS_REST_TOKEN,
  });
```

### Pagination from `offset/limit` to `page/perPage`

All pagination APIs now use `page` and `perPage` instead of `offset` and `limit`. This change provides a more intuitive pagination model that aligns with common web pagination patterns.

To migrate, update all pagination parameters from `offset/limit` to `page/perPage`. Note that `page` is 0-indexed.

```diff
memoryStore.listMessages({
    threadId: 'thread-123',
-   offset: 0,
-   limit: 20,
+   page: 0,
+   perPage: 20,
  });
```

### `getMessagesPaginated` to `listMessages`

The `getMessagesPaginated()` method has been replaced with `listMessages()`. The new method supports `perPage: false` to fetch all records without pagination. This change aligns with the `list*` naming convention and adds flexibility for fetching all records.

To migrate, rename the method and update pagination parameters. You can now use `perPage: false` to fetch all records.

```diff
+ const memoryStore = await storage.getStore('memory');
+
  // Paginated
- const result = await storage.getMessagesPaginated({
+ const result = await memoryStore?.listMessages({
    threadId: 'thread-123',
-   offset: 0,
-   limit: 20,
+   page: 0,
+   perPage: 20,
  });

  // Fetch all records (no pagination limit)
+ const allMessages = await memoryStore?.listMessages({
+   threadId: 'thread-123',
+   page: 0,
+   perPage: false,
+ });
```

> **Codemod:** You can use Mastra's codemod CLI to update your code automatically:
>
> ```bash
> npx @mastra/codemod@latest v1/storage-get-messages-paginated .
> ```

### Domain-specific storage access via `getStore()`

Storage operations are now accessed through domain-specific stores instead of directly on the storage instance.

Domains include:

- **`memory`** - Threads, messages, and resources
- **`workflows`** - Workflow snapshots
- **`scores`** - Evaluation scores
- **`observability`** - Traces and spans
- **`agents`** - Stored agent data

To migrate, call `getStore()` with the domain name, then call methods on the returned store.

```diff
const storage = mastra.getStorage();

  // Memory operations (threads, messages, resources)
- const thread = await storage.getThread({ threadId: '123' });
- await storage.saveThread({ thread });
+ const memoryStore = await storage.getStore('memory');
+ const thread = await memoryStore?.getThreadById({ threadId: '123' });
+ await memoryStore?.saveThread({ thread });

  // Workflow operations (snapshots)
- const snapshot = await storage.loadWorkflowSnapshot({ runId, workflowName });
- await storage.persistWorkflowSnapshot({ runId, workflowName, snapshot });
+ const workflowStore = await storage.getStore('workflows');
+ const snapshot = await workflowStore?.loadWorkflowSnapshot({ runId, workflowName });
+ await workflowStore?.persistWorkflowSnapshot({ runId, workflowName, snapshot });

  // Observability operations (traces, spans)
- const traces = await storage.listTraces({ page: 0, perPage: 20 });
+ const observabilityStore = await storage.getStore('observability');
+ const traces = await observabilityStore?.listTraces({ page: 0, perPage: 20 });

  // Score operations (evaluations)
- const scores = await storage.listScoresByScorerId({ scorerId: 'helpfulness' });
+ const scoresStore = await storage.getStore('scores');
+ const scores = await scoresStore?.listScoresByScorerId({ scorerId: 'helpfulness' });
```

### `getThreadsByResourceId` to `listThreads`

The `getThreadsByResourceId()` method has been replaced with `listThreads()`. The new method adds pagination support and filtering by `resourceId`, `metadata`, or both.

> **Important:** The old `getThreadsByResourceId()` returned all matching threads without pagination. The new `listThreads()` requires pagination parameters. To preserve the old behavior of fetching all threads, use `perPage: false`.

To migrate, use the memory store and the new `listThreads()` method with pagination and an optional filter object.

```diff
- const threads = await storage.getThreadsByResourceId({
-   resourceId: 'res-123',
- });
+ const memoryStore = await storage.getStore('memory');
+
+ // Paginated (recommended for large datasets)
+ const result = await memoryStore?.listThreads({
+   filter: { resourceId: 'res-123' },
+   page: 0,
+   perPage: 20,
+ });
+ const threads = result?.threads;
+
+ // Or fetch all threads like before (use perPage: false)
+ const allResult = await memoryStore?.listThreads({
+   filter: { resourceId: 'res-123' },
+   perPage: false,
+ });
+ const allThreads = allResult?.threads;
```

The new method also supports:

- Listing all threads (omit filter)
- Filtering by metadata only
- Combined resourceId + metadata filters

```typescript
// List all threads
await memoryStore?.listThreads({ page: 0, perPage: 20 })

// Filter by metadata only
await memoryStore?.listThreads({
  filter: { metadata: { status: 'active' } },
  page: 0,
  perPage: 20,
})

// Combined filter
await memoryStore?.listThreads({
  filter: {
    resourceId: 'user-123',
    metadata: { category: 'support' },
  },
  page: 0,
  perPage: 20,
})
```

> **Codemod:** You can use Mastra's codemod CLI to update your code automatically:
>
> ```bash
> npx @mastra/codemod@latest v1/storage-list-threads-by-resource-to-list-threads .
> ```

### `getWorkflowRuns` to `listWorkflowRuns`

The `getWorkflowRuns()` method has been renamed to `listWorkflowRuns()`. This change aligns with the convention that `list*` methods return collections.

To migrate, use the workflows stores, rename the method call and update pagination parameters.

```diff
- const runs = await storage.getWorkflowRuns({
+ const workflowStore = await storage.getStore('workflows');
+ const runs = await workflowStore?.listWorkflowRuns({
    fromDate,
    toDate,
+   page: 0,
+   perPage: 20,
  });
```

> **Codemod:** You can use Mastra's codemod CLI to update your code automatically:
>
> ```bash
> npx @mastra/codemod@latest v1/storage-list-workflow-runs .
> ```

### `getMessagesById` to `listMessagesById`

The `getMessagesById()` method has been renamed to `listMessagesById()`. This change aligns with the convention that `list*` methods return collections.

To migrate, use the memory store and rename the method call.

```diff
+ const memoryStore = await storage.getStore('memory');
- const result = await storage.getMessagesById({
+ const result = await memoryStore?.listMessagesById({
    messageIds: ['msg-1', 'msg-2'],
  });
```

> **Codemod:** You can use Mastra's codemod CLI to update your code automatically:
>
> ```bash
> npx @mastra/codemod@latest v1/storage-list-messages-by-id .
> ```

### Storage `getMessages` and `saveMessages` signatures

The `getMessages()` and `saveMessages()` methods have changed signatures and return types. Format overloads have been removed, and the methods now always work with `MastraDBMessage`. This change simplifies the API by removing format variations.

To migrate, use the memory store, remove format parameters, and update code to work with the consistent return type.

```diff
+ const memoryStore = await storage.getStore('memory');
+
  // Always returns { messages: MastraDBMessage[] }
- const v1Messages = await storage.getMessages({ threadId, format: 'v1' });
- const v2Messages = await storage.getMessages({ threadId, format: 'v2' });
+ const result = await memoryStore?.getMessages({ threadId });
+ const messages = result?.messages; // MastraDBMessage[]

  // SaveMessages always uses MastraDBMessage
- await storage.saveMessages({ messages: v1Messages, format: 'v1' });
- await storage.saveMessages({ messages: v2Messages, format: 'v2' });
+ const saveResult = await memoryStore?.saveMessages({ messages: mastraDBMessages });
+ const saved = saveResult?.messages; // MastraDBMessage[]
```

### Vector store API from positional to named arguments

All vector store methods now use named arguments instead of positional arguments. This change improves code readability and makes method signatures more maintainable.

To migrate, update all vector store method calls to use named arguments.

```diff
- await vectorDB.createIndex(indexName, 3, 'cosine');
+ await vectorDB.createIndex({
+   indexName: indexName,
+   dimension: 3,
+   metric: 'cosine',
+ });

- await vectorDB.upsert(indexName, [[1, 2, 3]], [{ test: 'data' }]);
+ await vectorDB.upsert({
+   indexName: indexName,
+   vectors: [[1, 2, 3]],
+   metadata: [{ test: 'data' }],
+ });

- await vectorDB.query(indexName, [1, 2, 3], 5);
+ await vectorDB.query({
+   indexName: indexName,
+   queryVector: [1, 2, 3],
+   topK: 5,
+ });
```

### Vector store method renames

The `updateIndexById` and `deleteIndexById` methods have been renamed to `updateVector` and `deleteVector` respectively. This change provides clearer naming that better describes the operations.

To migrate, rename the methods and use named arguments.

```diff
- await vectorDB.updateIndexById(indexName, id, update);
- await vectorDB.deleteIndexById(indexName, id);
+ await vectorDB.updateVector({ indexName, id, update });
+ await vectorDB.deleteVector({ indexName, id });
```

### PGVector constructor from connection string to object

The PGVector constructor now requires object parameters instead of a connection string. This change provides a more consistent API across all storage adapters.

To migrate, pass the connection string as an object property.

```diff
- const pgVector = new PgVector(process.env.POSTGRES_CONNECTION_STRING!);
+ const pgVector = new PgVector({
+   connectionString: process.env.POSTGRES_CONNECTION_STRING,
+ });
```

> **Codemod:** You can use Mastra's codemod CLI to update your code automatically:
>
> ```bash
> npx @mastra/codemod@latest v1/vector-pg-constructor .
> ```

### PGVector `defineIndex` to `buildIndex`

The `defineIndex()` method has been removed in favor of `buildIndex()`. This change provides clearer naming for the index building operation.

To migrate, rename the method and use named arguments.

```diff
- await vectorDB.defineIndex(indexName, 'cosine', { type: 'flat' });
+ await vectorDB.buildIndex({
+   indexName: indexName,
+   metric: 'cosine',
+   indexConfig: { type: 'flat' },
+ });
```

### `PostgresStore`: `schema` to `schemaName`

The `schema` parameter has been renamed to `schemaName` in the PostgresStore constructor. This change provides clearer naming to avoid confusion with database schema concepts.

To migrate, rename the parameter.

```diff
const pgStore = new PostgresStore({
    connectionString: process.env.POSTGRES_CONNECTION_STRING,
-   schema: customSchema,
+   schemaName: customSchema,
  });
```

> **Codemod:** You can use Mastra's codemod CLI to update your code automatically:
>
> ```bash
> npx @mastra/codemod@latest v1/storage-postgres-schema-name .
> ```

### Score storage methods to `listScoresBy*` pattern

Score storage APIs have been renamed to follow the `listScoresBy*` pattern. This change provides consistency with the broader API naming conventions.

To migrate, update method names from `getScores` to `listScoresByScorerId` and related variants.

```diff
- const scores = await storage.getScores({ scorerName: 'helpfulness-scorer' });
+ const scores = await storage.listScoresByScorerId({
+   scorerId: 'helpfulness-scorer',
+ });
+ // Also available: listScoresByRunId, listScoresByEntityId, listScoresBySpan
```

## Removed

### Non-paginated storage functions

Non-paginated storage functions have been removed in favor of paginated versions. All list operations now use pagination, though you can fetch all records with `perPage: false`. This change provides consistency across the API and prevents accidental loading of large datasets.

To migrate, use paginated methods via domain stores. For fetching all records, use `perPage: false`.

```diff
- // Non-paginated direct access
- const messages = await storage.getMessages({ threadId });

+ // Use paginated methods via domain stores
+ const memoryStore = await storage.getStore('memory');
+ const result = await memoryStore?.listMessages({ threadId, page: 0, perPage: 20 });
+ // Or fetch all
+ const allMessages = await memoryStore?.listMessages({
+   threadId,
+   page: 0,
+   perPage: false,
+ });
```

### `getTraces` and `getTracesPaginated`

The `getTraces()` and `getTracesPaginated()` methods have been removed from storage. Traces are now handled through the observability package rather than core storage. This change provides better separation of concerns between core storage and observability features.

To migrate, use observability storage methods instead.

```diff
- const traces = await storage.getTraces({ traceId: 'trace-123' });
- const paginated = await storage.getTracesPaginated({ page: 0, perPage: 20 });

+ // Use observability API for traces
+ import { initObservability } from '@mastra/observability';
+ const observability = initObservability({ config: { ... } });
+ // Access traces through observability API
```

### Evals test utilities

Evals domain test utilities have been removed from `@internal/test-utils`. This change reflects the removal of legacy evals functionality.

To migrate, use storage APIs directly for testing instead of specialized evals test utilities.

```diff
- import { createEvalsTests } from '@internal/test-utils/domains/evals';
- createEvalsTests({ storage });

+ // Use storage APIs directly for testing
```

### TABLE\_EVALS from MSSQL storage

The `TABLE_EVALS` table has been removed from MSSQL storage implementations. This change reflects the removal of legacy evals functionality.

If you were using MSSQL storage with evals, migrate to a different storage adapter or remove evals functionality.