Skip to main content

Convex storage

The Convex storage implementation provides a serverless storage solution using Convex, a full-stack TypeScript development platform with real-time sync and automatic caching.

Observability Not Supported

Convex storage doesn't support the observability domain. Traces from the MastraStorageExporter can't be persisted to Convex, and Studio's observability features won't work with Convex as your only storage provider. To enable observability, use composite storage to route observability data to a supported provider like ClickHouse.

Record Size Limit

Convex enforces a 1 MiB maximum record size. This limit can be exceeded when storing messages with base64-encoded attachments such as images. See Handling large attachments for workarounds including uploading attachments to external storage like S3, Cloudflare R2, or Convex file storage.

Installation
Direct link to Installation

npm install @mastra/convex@latest

Convex setup
Direct link to Convex setup

Before using ConvexStore, set up the Convex schema and storage handler in your Convex project. The schema example below includes the full ConvexStore and ConvexServerCache setup. If you only use ConvexStore, omit mastraCacheTable and mastraCacheListItemsTable; if you use ConvexServerCache, include those tables and create the cache handler.

1. Set up Convex Schema
Direct link to 1. Set up Convex Schema

In convex/schema.ts:

import { defineSchema } from 'convex/server'
import {
mastraThreadsTable,
mastraMessagesTable,
mastraResourcesTable,
mastraWorkflowSnapshotsTable,
mastraScoresTable,
mastraVectorIndexesTable,
mastraVectorsTable,
mastraCacheTable,
mastraCacheListItemsTable,
mastraDocumentsTable,
} from '@mastra/convex/schema'

export default defineSchema({
mastra_threads: mastraThreadsTable,
mastra_messages: mastraMessagesTable,
mastra_resources: mastraResourcesTable,
mastra_workflow_snapshots: mastraWorkflowSnapshotsTable,
mastra_scorers: mastraScoresTable,
mastra_vector_indexes: mastraVectorIndexesTable,
mastra_vectors: mastraVectorsTable,
mastra_cache: mastraCacheTable,
mastra_cache_list_items: mastraCacheListItemsTable,
mastra_documents: mastraDocumentsTable,
})

2. Create the Storage Handler
Direct link to 2. Create the Storage Handler

In convex/mastra/storage.ts:

import { mastraStorage } from '@mastra/convex/server'

export const handle = mastraStorage

If you use ConvexServerCache, create convex/mastra/cache.ts:

import { mastraCache } from '@mastra/convex/server'

export const handle = mastraCache

3. Deploy to Convex
Direct link to 3. Deploy to Convex

npx convex dev
# or for production
npx convex deploy

Usage
Direct link to Usage

import { ConvexServerCache, ConvexStore } from '@mastra/convex'

const storage = new ConvexStore({
id: 'convex-storage',
deploymentUrl: process.env.CONVEX_URL!,
adminAuthToken: process.env.CONVEX_ADMIN_KEY!,
})

const cache = new ConvexServerCache({
deploymentUrl: process.env.CONVEX_URL!,
adminAuthToken: process.env.CONVEX_ADMIN_KEY!,
})

ConvexStore parameters
Direct link to ConvexStore parameters

deploymentUrl:

string
Convex deployment URL (e.g., https://your-project.convex.cloud)

adminAuthToken:

string
Convex admin authentication token for backend access

storageFunction?:

string
= mastra/storage:handle
Path to the storage mutation function (default: 'mastra/storage:handle')

ConvexServerCache parameters
Direct link to ConvexServerCache parameters

deploymentUrl:

string
Convex deployment URL (e.g., https://your-project.convex.cloud)

adminAuthToken:

string
Convex admin authentication token for backend access

cacheFunction?:

string
= mastra/cache:handle
Path to the cache mutation function for ConvexServerCache (default: 'mastra/cache:handle')

requestTimeoutMs?:

number
= 30000
Timeout for Convex cache mutation requests in milliseconds. Set to 0 to disable the client-side timeout.

keyPrefix?:

string
= mastra:cache:
Prefix applied to ConvexServerCache keys. clear() removes rows whose stored prefix exactly matches this value.

ttlMs?:

number
= 300000
Default ConvexServerCache TTL in milliseconds. Set to 0 to disable expiry.

Constructor examples
Direct link to Constructor examples

import { ConvexServerCache, ConvexStore } from '@mastra/convex'

// Basic configuration
const store = new ConvexStore({
id: 'convex-storage',
deploymentUrl: 'https://your-project.convex.cloud',
adminAuthToken: 'your-admin-token',
})

// With custom storage function path
const storeCustom = new ConvexStore({
id: 'convex-storage',
deploymentUrl: 'https://your-project.convex.cloud',
adminAuthToken: 'your-admin-token',
storageFunction: 'custom/path:handler',
})

// Server cache for durable stream replay and response caching
const cache = new ConvexServerCache({
deploymentUrl: 'https://your-project.convex.cloud',
adminAuthToken: 'your-admin-token',
cacheFunction: 'mastra/cache:handle',
})

Server cache
Direct link to Server cache

ConvexServerCache implements Mastra's server cache contract with Convex. Use it when you want durable cache state for features such as resumable durable-agent streams, workflow stream replay, or response caching.

ConvexServerCache stores list entries as separate Convex documents. This avoids growing a stream replay list inside one document and helps stay within Convex's record size limit.

Each scalar cache value and each list item is stored as one Convex row and must stay within Convex's row-size limits. Very large lists are still bounded by Convex query limits when replaying a range.

Cache cleanup and clear() run in bounded batches. A single client call can loop through up to 1,000 Convex mutations, with each mutation handling up to 25 list items. While clear() is cleaning a key, reads for that key can return empty results until cleanup finishes.

For very large cache namespaces, clear incrementally or use narrower prefixes to avoid long-running cleanup operations.

During batched cleanup, cache metadata can temporarily use an internal deleted state. The next cleanup pass removes those rows. Avoid writing new values with the same prefix until clear() finishes.

clear() only removes rows whose stored keyPrefix exactly matches the configured keyPrefix. It does not clear nested prefixes by string prefix matching. Each listPush() refreshes the list TTL using the cache's configured ttlMs.

Use a non-empty keyPrefix unless you intentionally want clear() to remove every cache key in the deployment. Expired list rows are reclaimed incrementally during reads and writes; clear() removes all rows for the prefix.

ConvexServerCache works best for durable replay of moderate-frequency events. For high-frequency token streams, prefer batching events or using a lower-latency cache backend.

ConvexServerCache does not replace a distributed pub/sub transport. If your app needs live cross-process event delivery, configure a production pub/sub backend separately.

Additional notes
Direct link to Additional notes

Schema Management
Direct link to Schema Management

The storage implementation uses typed Convex tables for each Mastra domain:

DomainConvex TablePurpose
Threadsmastra_threadsConversation threads
Messagesmastra_messagesChat messages
Resourcesmastra_resourcesUser working memory
Workflowsmastra_workflow_snapshotsWorkflow state
Scorersmastra_scorersEvaluation data
Cachemastra_cacheCache values, counters, and list metadata
Cache Itemsmastra_cache_list_itemsCache list entries
Fallbackmastra_documentsUnknown tables

Architecture
Direct link to Architecture

All typed tables include:

  • An id field for Mastra's record ID (distinct from Convex's auto-generated _id)
  • A by_record_id index for efficient lookups by Mastra ID

This design ensures compatibility with Mastra's storage contract while leveraging Convex's automatic indexing and real-time capabilities.

Environment variables
Direct link to Environment variables

Set these environment variables for your deployment:

  • CONVEX_URL – Your Convex deployment URL
  • CONVEX_ADMIN_KEY – Admin authentication token (get from Convex dashboard)