@mastra/core
Version:
Mastra is a framework for building AI-powered applications and agents with a modern TypeScript stack.
260 lines (198 loc) • 9.52 kB
Markdown
# Storage
For agents to remember previous interactions, Mastra needs a storage adapter. Use one of the [supported providers](#supported-providers) and pass it to your Mastra instance.
```typescript
import { Mastra } from '@mastra/core'
import { LibSQLStore } from '@mastra/libsql'
export const mastra = new Mastra({
storage: new LibSQLStore({
id: 'mastra-storage',
url: 'file:./mastra.db',
}),
})
```
> **Sharing the database with Studio:** When running `mastra dev` alongside your application (e.g., Next.js), use an absolute path to ensure both processes access the same database:
>
> ```typescript
> url: 'file:/absolute/path/to/your/project/mastra.db'
> ```
>
> Relative paths like `file:./mastra.db` resolve based on each process's working directory, which may differ.
This configures instance-level storage, which all agents share by default. You can also configure [agent-level storage](#agent-level-storage) for isolated data boundaries.
Mastra automatically initializes the necessary storage structures on first interaction. See [Storage Overview](https://mastra.ai/reference/storage/overview) for domain coverage and the schema used by the built-in database-backed domains.
## Supported providers
Each provider page includes installation instructions, configuration parameters, and usage examples:
- [libSQL](https://mastra.ai/reference/storage/libsql)
- [PostgreSQL](https://mastra.ai/reference/storage/postgresql)
- [MongoDB](https://mastra.ai/reference/storage/mongodb)
- [Upstash](https://mastra.ai/reference/storage/upstash)
- [Redis](https://mastra.ai/reference/storage/redis)
- [Cloudflare D1](https://mastra.ai/reference/storage/cloudflare-d1)
- [Cloudflare KV & Durable Objects](https://mastra.ai/reference/storage/cloudflare)
- [Convex](https://mastra.ai/reference/storage/convex)
- [DynamoDB](https://mastra.ai/reference/storage/dynamodb)
- [LanceDB](https://mastra.ai/reference/storage/lance)
- [Microsoft SQL Server](https://mastra.ai/reference/storage/mssql)
> **Tip:** libSQL is the easiest way to get started because it doesn’t require running a separate database server.
## Configuration scope
Storage can be configured at the instance level (shared by all agents) or at the agent level (isolated to a specific agent).
### Instance-level storage
Add storage to your Mastra instance so all agents, workflows, observability traces, and scores share the same storage backend:
```typescript
import { Mastra } from '/core'
import { PostgresStore } from '/pg'
export const mastra = new Mastra({
storage: new PostgresStore({
id: 'mastra-storage',
connectionString: process.env.DATABASE_URL,
}),
})
// Both agents inherit storage from the Mastra instance above
const agent1 = new Agent({ id: 'agent-1', memory: new Memory() })
const agent2 = new Agent({ id: 'agent-2', memory: new Memory() })
```
This is useful when all primitives share the same storage backend and have similar performance, scaling, and operational requirements.
#### Composite storage
[Composite storage](https://mastra.ai/reference/storage/composite) is an alternative way to configure instance-level storage. Use `MastraCompositeStore` to route `memory` and any other [supported domains](https://mastra.ai/reference/storage/composite) to different storage providers.
```typescript
import { Mastra } from '/core'
import { MastraCompositeStore } from '/core/storage'
import { MemoryLibSQL } from '/libsql'
import { WorkflowsPG } from '/pg'
import { ObservabilityStorageClickhouse } from '/clickhouse'
export const mastra = new Mastra({
storage: new MastraCompositeStore({
id: 'composite',
domains: {
memory: new MemoryLibSQL({ url: 'file:./memory.db' }),
workflows: new WorkflowsPG({ connectionString: process.env.DATABASE_URL }),
observability: new ObservabilityStorageClickhouse({
url: process.env.CLICKHOUSE_URL,
username: process.env.CLICKHOUSE_USERNAME,
password: process.env.CLICKHOUSE_PASSWORD,
}),
},
}),
})
```
This is useful when different types of data have different performance or operational requirements, such as low-latency storage for memory, durable storage for workflows, and high-throughput storage for observability.
### Agent-level storage
Agent-level storage overrides storage configured at the instance level. Add storage to a specific agent when you need to keep data separate or use different providers per agent.
```typescript
import { Agent } from '/core/agent'
import { Memory } from '/memory'
import { PostgresStore } from '/pg'
export const agent = new Agent({
id: 'agent',
memory: new Memory({
storage: new PostgresStore({
id: 'agent-storage',
connectionString: process.env.AGENT_DATABASE_URL,
}),
}),
})
```
## Threads and resources
Mastra organizes conversations using two identifiers:
- **Thread**: A conversation session containing a sequence of messages.
- **Resource**: The entity that owns the thread, such as a user, organization, project, or any other domain entity in your application.
Both identifiers are required for agents to store information:
**.generate()**:
```typescript
const response = await agent.generate('hello', {
memory: {
thread: 'conversation-abc-123',
resource: 'user_123',
},
})
```
**.stream()**:
```typescript
const stream = await agent.stream('hello', {
memory: {
thread: 'conversation-abc-123',
resource: 'user_123',
},
})
```
> **Note:** [Studio](https://mastra.ai/docs/studio/overview) automatically generates a thread and resource ID for you. When calling `stream()` or `generate()` yourself, remember to provide these identifiers explicitly.
### Thread title generation
Mastra can automatically generate descriptive thread titles based on the user's first message when `generateTitle` is enabled.
Use this option when implementing a ChatGPT-style chat interface to render a title alongside each thread in the conversation list (for example, in a sidebar) derived from the thread’s initial user message.
```typescript
export const agent = new Agent({
id: 'agent',
memory: new Memory({
options: {
generateTitle: true,
},
}),
})
```
Title generation runs asynchronously after the agent responds and doesn't affect response time.
To optimize cost or behavior, provide a smaller [`model`](https://mastra.ai/models) and custom `instructions`:
```typescript
export const agent = new Agent({
id: 'agent',
memory: new Memory({
options: {
generateTitle: {
model: 'openai/gpt-5-mini',
instructions: 'Generate a 1 word title',
},
},
}),
})
```
## Semantic recall
Semantic recall has different storage requirements - it needs a vector database in addition to the standard storage adapter. See [Semantic recall](https://mastra.ai/docs/memory/semantic-recall) for setup and supported vector providers.
## Handling large attachments
Some storage providers enforce record size limits that base64-encoded file attachments (such as images) can exceed:
| Provider | Record size limit |
| ------------------------------------------------------------------ | ----------------- |
| [DynamoDB](https://mastra.ai/reference/storage/dynamodb) | 400 KB |
| [Convex](https://mastra.ai/reference/storage/convex) | 1 MiB |
| [Cloudflare D1](https://mastra.ai/reference/storage/cloudflare-d1) | 1 MiB |
PostgreSQL, MongoDB, and libSQL have higher limits and are generally unaffected.
To avoid this, use an input processor to upload attachments to external storage (S3, R2, GCS, [Convex file storage](https://docs.convex.dev/file-storage), etc.) and replace them with URL references before persistence.
```typescript
import type { Processor } from '/core/processors'
import type { MastraDBMessage } from '/core/memory'
export class AttachmentUploader implements Processor {
id = 'attachment-uploader'
async processInput({ messages }: { messages: MastraDBMessage[] }) {
return Promise.all(messages.map(msg => this.processMessage(msg)))
}
async processMessage(msg: MastraDBMessage) {
const attachments = msg.content.experimental_attachments
if (!attachments?.length) return msg
const uploaded = await Promise.all(
attachments.map(async att => {
// Skip if already a URL
if (!att.url?.startsWith('data:')) return att
// Upload base64 data and replace with URL
const url = await this.upload(att.url, att.contentType)
return { ...att, url }
}),
)
return { ...msg, content: { ...msg.content, experimental_attachments: uploaded } }
}
async upload(dataUri: string, contentType?: string): Promise<string> {
const base64 = dataUri.split(',')[1]
const buffer = Buffer.from(base64, 'base64')
// Replace with your storage provider (S3, R2, GCS, Convex, etc.)
// return await s3.upload(buffer, contentType);
throw new Error('Implement upload() with your storage provider')
}
}
```
Use the processor with your agent:
```typescript
import { Agent } from '/core/agent'
import { Memory } from '/memory'
import { AttachmentUploader } from './processors/attachment-uploader'
const agent = new Agent({
id: 'my-agent',
memory: new Memory({ storage: yourStorage }),
inputProcessors: [new AttachmentUploader()],
})
```