# 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)
- [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 '@mastra/core'
import { PostgresStore } from '@mastra/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 '@mastra/core'
import { MastraCompositeStore } from '@mastra/core/storage'
import { MemoryLibSQL } from '@mastra/libsql'
import { WorkflowsPG } from '@mastra/pg'
import { ObservabilityStorageClickhouse } from '@mastra/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 '@mastra/core/agent'
import { Memory } from '@mastra/memory'
import { PostgresStore } from '@mastra/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 '@mastra/core/processors'
import type { MastraDBMessage } from '@mastra/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 '@mastra/core/agent'
import { Memory } from '@mastra/memory'
import { AttachmentUploader } from './processors/attachment-uploader'

const agent = new Agent({
  id: 'my-agent',
  memory: new Memory({ storage: yourStorage }),
  inputProcessors: [new AttachmentUploader()],
})
```