MongoDB vector store
The MongoDBVector class provides vector search using MongoDB Atlas Vector Search. It enables efficient similarity search and metadata filtering within your MongoDB collections.
InstallationDirect link to Installation
- npm
- pnpm
- Yarn
- Bun
npm install @mastra/mongodb@latest
pnpm add @mastra/mongodb@latest
yarn add @mastra/mongodb@latest
bun add @mastra/mongodb@latest
Usage exampleDirect link to Usage example
import { MongoDBVector } from '@mastra/mongodb'
const store = new MongoDBVector({
id: 'mongodb-vector',
uri: process.env.MONGODB_URI,
dbName: process.env.MONGODB_DB_NAME,
})
Custom Embedding Field PathDirect link to Custom Embedding Field Path
If you need to store embeddings in a nested field structure (e.g., to integrate with existing MongoDB collections), use the embeddingFieldPath option:
import { MongoDBVector } from '@mastra/mongodb'
const store = new MongoDBVector({
id: 'mongodb-vector',
uri: process.env.MONGODB_URI,
dbName: process.env.MONGODB_DB_NAME,
embeddingFieldPath: 'text.contentEmbedding', // Store embeddings at text.contentEmbedding
})
Constructor optionsDirect link to Constructor options
id:
uri:
dbName:
options?:
embeddingFieldPath?:
MethodsDirect link to Methods
connect()Direct link to connect
Establishes connection to the MongoDB server. This is called automatically on first use, but can be called explicitly if needed.
await store.connect()
createIndex()Direct link to createindex
Creates a new vector index (collection) in MongoDB.
indexName:
dimension:
metric?:
filterFields?:
metadata.<field>). Queries that filter only on declared fields are pushed directly into $vectorSearch instead of pre-filtering candidate _ids, avoiding the 16 MB BSON limit on large result sets. Filters that reference an undeclared field, or use an operator $vectorSearch does not support, fall back to the pre-filter automatically.collectionName?:
indexName.searchIndexName?:
${indexName}_vector_index.allowWrites?:
upsert, updateVector, deleteVector, deleteVectors) on a bring-your-own collection. By default a BYO index is read-only: the store never modifies or deletes caller-owned operational documents. Ignored for managed collections, which are always writable. The policy is persisted with the index registration and survives restarts.waitForIndexReady()Direct link to waitforindexready
Waits for an index to become ready after creation. Useful when you need to ensure an index is ready before performing operations.
indexName:
timeoutMs?:
checkIntervalMs?:
upsert()Direct link to upsert
Adds or updates vectors and their metadata in the collection. On a bring-your-own index, this requires allowWrites: true at createIndex() time because BYO collections are read-only by default.
indexName:
vectors:
metadata?:
ids?:
documents?:
query()Direct link to query
Searches for similar vectors with optional metadata filtering.
indexName:
queryVector:
topK?:
filter?:
metadata field)documentFilter?:
includeVector?:
numCandidates?:
metadataMode?:
'field' (default) projects the managed metadata/document fields, and filter fields are matched against the metadata subdocument. 'document' returns the full source document as metadata — use for bring-your-own operational collections whose documents have their own shape — and filter fields are matched against the **root** document (no metadata. prefix). The embedding field is omitted from metadata by default (to avoid payload bloat); set includeVector: true to retain it in metadata and also expose it as a top-level vector.createSearchIndex()Direct link to createsearchindex
Provisions an Atlas Search (BM25/full-text) index on the collection backing an index and records it as the text-search index that textQuery() and hybridQuery() will target.
Managed vs. bring-your-own collections:
- For a managed index (created without
collectionName),createIndex()already provisions a dynamic full-text index named${collectionName}_search_index(covering all string fields).createSearchIndex()is therefore only needed when you want a field-restricted mapping or a custom index name. - For a bring-your-own index (created with
collectionName),createIndex()doesn't auto-create any full-text index. EnablingtextQuery()/hybridQuery()on a caller-owned operational collection is opt-in. CallcreateSearchIndex()explicitly to provision the (billable) text index. Until you do,textQuery()/hybridQuery()throw a clear error rather than querying a non-existent index.
Naming:
- When
fieldsis provided without an explicitsearchIndexName, the field-mapped index is created under a distinct default name (${collectionName}_${indexName}_search_fields_index, unique per logical index) so it doesn't collide with a managed collection's auto-created dynamic index and get silently ignored. This distinct index is persisted as the text-search index, sotextQuery()/hybridQuery()use the restricted mapping automatically. - When
searchIndexNameis provided, that exact name is used and persisted.textQuery()/hybridQuery()resolve the persisted name automatically. You can also override the name per call via theirsearchIndexName/textSearchIndexNameparameters.
indexName:
fields?:
searchIndexName?:
fields is provided and this is omitted, a distinct default name that is unique per logical index is used, so the field mapping is not shadowed by the auto-created dynamic index and two logical indexes on the same collection do not collide.waitUntilReady?:
waitForSearchIndexReady() explicitly if you prefer to await separately.await store.createSearchIndex({
indexName: 'precedents',
fields: ['note', 'description'],
})
The field-mapped index name includes the logical indexName, so two logical indexes on the same collection get distinct text indexes. Recreating the same logical index with different fields still requires dropping the existing index first (IndexAlreadyExists).
waitForSearchIndexReady()Direct link to waitforsearchindexready
Waits for the full-text (BM25) search index of an index to become READY. waitForIndexReady() polls only the vectorSearch index; createSearchIndex() returns while the Atlas Search full-text index is still building, so an immediate textQuery()/hybridQuery() can intermittently fail. Call this (or pass waitUntilReady: true to createSearchIndex()) to block until the resolved text index reports READY.
indexName:
searchIndexName?:
timeoutMs?:
checkIntervalMs?:
await store.createSearchIndex({ indexName: 'precedents', fields: ['note'] })
await store.waitForSearchIndexReady({ indexName: 'precedents' })
textQuery()Direct link to textquery
Runs a full-text (BM25) search against an Atlas Search index. By default it targets the text-search index recorded for this index (set by createSearchIndex(), or the dynamic ${collectionName}_search_index auto-created by createIndex()). Pass searchIndexName to target a specific index for this call.
Metadata filters here (like hybridQuery()) are applied via a $match stage. For the vector branch of hybridQuery(), filters on fields not declared via filterFields at index creation are transparently materialised as candidate _ids (the same fallback query() uses), so undeclared-field filters don't error.
indexName:
query:
paths:
topK?:
filter?:
metadata field)metadataMode?:
'field' (default) projects the managed metadata/document fields. 'document' returns the full source document as metadata.searchIndexName?:
createSearchIndex() / createIndex().const results = await store.textQuery({
indexName: 'precedents',
query: 'shell company offshore',
paths: ['note'],
topK: 10,
})
hybridQuery()Direct link to hybridquery
Runs a hybrid search that fuses vector similarity and full-text results using MongoDB's server-side $rankFusion. It requires MongoDB >= 8.0 and is generally available from 8.1. On 8.0.x, it may need a MongoDB support case to enable, and it runs where enabled, such as Atlas 8.0.x. A full-text search index must exist: it's auto-created for managed indexes, but for a bring-your-own collection you must call createSearchIndex() first (opt-in).
indexName:
queryVector:
query:
paths:
topK?:
filter?:
weights?:
numCandidates?:
metadataMode?:
'field' (default) projects the managed metadata/document fields. 'document' returns the full source document as metadata.textSearchIndexName?:
createSearchIndex() / createIndex().const results = await store.hybridQuery({
indexName: 'precedents',
queryVector: embedding,
query: 'shell company offshore',
paths: ['note'],
topK: 10,
weights: { vector: 1, text: 1.5 }, // Favor text matches
})
hybridQuery() requires MongoDB >= 8.0 for the $rankFusion stage. The stage is generally available from 8.1. On 8.0.x, it may need a MongoDB support case to enable and runs where enabled, such as Atlas 8.0.x. If you're running an older version, or $rankFusion isn't enabled on your 8.0.x deployment, use query() and textQuery() separately and merge the results client-side.
describeIndex()Direct link to describeindex
Returns information about the index (collection).
indexName:
Returns:
interface IndexStats {
dimension: number
count: number
metric: 'cosine' | 'euclidean' | 'dotproduct'
}
deleteIndex()Direct link to deleteindex
Deletes a vector index. Behavior depends on how the index was created:
- Managed index (created without
collectionName): drops the entire collection and all its data. - Bring-your-own index (created with
collectionName): drops the Atlas vectorSearch index and, if one was provisioned viacreateSearchIndex(), the companion full-text search index. The caller's operational collection and its documents are preserved. This store never drops a collection it didn't create.
The BYO classification is recorded durably when the index is created, so it's applied correctly even by a different process (e.g. an index created by a setup job and later deleted by a long-lived service). Always pass the logical index name (the indexName used at createIndex), not the physical collection name.
indexName:
listIndexes()Direct link to listindexes
Lists the logical Mastra index names (the indexName values passed to createIndex), not physical collection names. For a bring-your-own index whose data lives in an operational collection, the logical index name is returned instead of the physical collection name. The value can be passed straight back into deleteIndex() / describeIndex(). Managed indexes created before durable metadata was introduced are still discovered via their ${name}_vector_index search index. The internal registry collection is never listed.
Returns: Promise<string[]>
updateVector()Direct link to updatevector
Update a single vector by ID or by metadata filter. Either id or filter must be provided, but not both.
Bring-your-own collections are read-only by default.
upsert(),updateVector(),deleteVector(), anddeleteVectors()throw a USER-category error on a BYO index unless it was created withallowWrites: true. See Indexing an existing collection.
indexName:
id?:
filter?:
update:
update.vector?:
update.metadata?:
deleteVector()Direct link to deletevector
Deletes a specific vector entry from an index by its ID.
indexName:
id:
deleteVectors()Direct link to deletevectors
Delete multiple vectors by IDs or by metadata filter. Either ids or filter must be provided, but not both.
indexName:
ids?:
filter?:
disconnect()Direct link to disconnect
Closes the MongoDB client connection. Should be called when done using the store.
Response typesDirect link to Response types
Query results are returned in this format:
interface QueryResult {
id: string
score: number
metadata: Record<string, any>
vector?: number[] // Only included if includeVector is true
}
Error handlingDirect link to Error handling
The store throws typed errors that can be caught:
try {
await store.query({
indexName: 'my_collection',
queryVector: queryVector,
})
} catch (error) {
// Handle specific error cases
if (error.message.includes('Invalid collection name')) {
console.error(
'Collection name must start with a letter or underscore and contain only valid characters.',
)
} else if (error.message.includes('Collection not found')) {
console.error('The specified collection does not exist')
} else {
console.error('Vector store error:', error.message)
}
}
Indexing an existing collectionDirect link to Indexing an existing collection
You can create a vector index on an existing operational collection instead of using a managed collection. This is useful when you want to add vector search capabilities to documents that already exist in your MongoDB database.
import { MongoDBVector } from '@mastra/mongodb'
const store = new MongoDBVector({
id: 'mongodb-vector',
uri: process.env.MONGODB_URI,
dbName: process.env.MONGODB_DB_NAME,
})
// Create a vector index on an existing 'transactions' collection
await store.createIndex({
indexName: 'precedents',
dimension: 1024,
collectionName: 'transactions', // Use existing collection
searchIndexName: 'txn_vec_idx', // Custom search index name
})
// Wait for the index to be ready
await store.waitForIndexReady({ indexName: 'precedents' })
// Query using document mode to get full source documents
const hits = await store.query({
indexName: 'precedents',
queryVector: embeddings,
topK: 5,
metadataMode: 'document', // Returns full document as metadata
})
// hits[0].metadata now contains all fields from the source document
console.log(hits[0].metadata.amount, hits[0].metadata.customField)
// Full-text / hybrid search on a BYO collection is opt-in: provision the text index first.
await store.createSearchIndex({ indexName: 'precedents', fields: ['note'] })
Important notes:
- The collection must already exist and contain documents with an
embeddingfield (or the customembeddingFieldPathyou configured) - The collection is never created or dropped when using
collectionName - A BYO index is read-only by default.
upsert(),updateVector(),deleteVector(), anddeleteVectors()throw a clear error rather than mutating caller-owned operational documents. To let the store write embeddings into (or delete documents from) your collection, opt in explicitly withcreateIndex({ ..., allowWrites: true }). The policy is persisted and survives restarts. Entries written by older versions without the flag are treated as read-only (fail closed). - Use
metadataMode: 'document'when querying to retrieve the full source document asmetadata - In
'document'mode the embedding is omitted frommetadataby default; passincludeVector: trueto retain it (and also expose it as a top-levelvector) - Filtering in
'document'mode operates on root document fields, not a nestedmetadata.subdocument.filter: { lane: 'fraud' }matches the top-levellanefield of your operational documents (in the default'field'mode, bare fields are rewritten tometadata.<field>for managed collections). Both the pushdown and$matchfallback paths honor this. - Native
ObjectId_ids are supported. Operational collections commonly key onObjectId; query results coerce_idto a string (theQueryResult.idcontract), anddeleteVector()/updateVector()/deleteVectors()accept that string and match the underlyingObjectIddocument. Managed collections (string_ids) are unaffected. - Full-text and hybrid search on a BYO collection are opt-in: no full-text index is auto-created, so call
createSearchIndex()beforetextQuery()/hybridQuery(). The full-text index builds asynchronously. CallwaitForSearchIndexReady()(or passwaitUntilReady: true) before an immediate text/hybrid query. deleteIndex()on a BYO index drops the vector index (and the text index if one was created) but preserves the collection and its documents
Best practicesDirect link to Best practices
- Index metadata fields used in filters for optimal query performance.
- Use consistent field naming in metadata to avoid unexpected query results.
- Regularly monitor index and collection statistics to ensure efficient search.
- When indexing existing collections, ensure all documents have the required
embeddingfield.
Usage exampleDirect link to Usage example
Vector embeddings with MongoDBDirect link to vector-embeddings-with-mongodb
Embeddings are numeric vectors used by memory's semanticRecall to retrieve related messages by meaning (not keywords).
MongoDB Atlas Vector Search is recommended for production use. For self-hosted deployments, Vector Search is available with local Atlas deployments via the Atlas CLI.
This setup uses FastEmbed, a local embedding model, to generate vector embeddings.
To use this, install @mastra/fastembed:
- npm
- pnpm
- Yarn
- Bun
npm install @mastra/fastembed@latest
pnpm add @mastra/fastembed@latest
yarn add @mastra/fastembed@latest
bun add @mastra/fastembed@latest
Add the following to your agent:
import { Memory } from '@mastra/memory'
import { Agent } from '@mastra/core/agent'
import { MongoDBStore, MongoDBVector } from '@mastra/mongodb'
import { fastembed } from '@mastra/fastembed'
export const mongodbAgent = new Agent({
id: 'mongodb-agent',
name: 'mongodb-agent',
instructions:
'You are an AI agent with the ability to automatically recall memories from previous interactions.',
model: 'openai/gpt-5.6-sol',
memory: new Memory({
storage: new MongoDBStore({
id: 'mongodb-storage',
uri: process.env.MONGODB_URI!,
dbName: process.env.MONGODB_DB_NAME!,
}),
vector: new MongoDBVector({
id: 'mongodb-vector',
uri: process.env.MONGODB_URI!,
dbName: process.env.MONGODB_DB_NAME!,
}),
embedder: fastembed,
options: {
lastMessages: 10,
semanticRecall: {
topK: 3,
messageRange: 2,
},
generateTitle: true, // generates descriptive thread titles automatically
},
}),
})
Vector embeddings with VoyageAIDirect link to Vector embeddings with VoyageAI
VoyageAI provides specialized embedding models optimized for retrieval tasks. VoyageAI is also integrated with MongoDB Atlas for multimodal embeddings.
- npm
- pnpm
- Yarn
- Bun
npm install @mastra/voyageai@latest
pnpm add @mastra/voyageai@latest
yarn add @mastra/voyageai@latest
bun add @mastra/voyageai@latest
Basic usage example:
import { Memory } from '@mastra/memory'
import { Agent } from '@mastra/core/agent'
import { MongoDBStore, MongoDBVector } from '@mastra/mongodb'
import { voyage } from '@mastra/voyageai'
export const mongodbVoyageAgent = new Agent({
id: 'mongodb-voyage-agent',
name: 'MongoDB VoyageAI Agent',
instructions: 'You are an AI agent with semantic recall powered by VoyageAI and MongoDB.',
model: 'openai/gpt-5.6-sol',
memory: new Memory({
storage: new MongoDBStore({
id: 'mongodb-storage',
uri: process.env.MONGODB_URI!,
dbName: process.env.MONGODB_DB_NAME!,
}),
vector: new MongoDBVector({
id: 'mongodb-vector',
uri: process.env.MONGODB_URI!,
dbName: process.env.MONGODB_DB_NAME!,
}),
embedder: voyage, // VoyageAI's default model (voyage-3.5, 1024 dimensions)
options: {
lastMessages: 10,
semanticRecall: {
topK: 5,
messageRange: 2,
},
},
}),
})
For detailed VoyageAI embedding examples including specialized models, multimodal embeddings, and retrieval optimization, see the VoyageAI embeddings documentation.