docs/atlas-search.md
Atlas Search enables fine-grained text indexing and querying of data on your Atlas cluster. You can use Atlas Search to build fast, relevance-based search capabilities on top of your MongoDB data.
Mongoose provides full support for managing Atlas Search indexes through your schema definitions, and querying with the $search aggregation stage.
You can define Atlas Search indexes in your Mongoose schema using schema.searchIndex() and Model.createSearchIndexes() to create the indexes.
Mongoose can optionally create the search indexes for you when your model initializes if you enable the autoSearchIndex option.
const movieSchema = new mongoose.Schema({
title: String,
fullplot: String,
genres: [String],
cast: [String],
year: Number
});
// Define a basic text search index
movieSchema.searchIndex({
name: 'movie_search',
definition: {
mappings: {
dynamic: false,
fields: {
title: { type: 'string' },
fullplot: { type: 'string' },
cast: { type: 'string' },
year: { type: 'number' }
}
}
}
});
const Movie = mongoose.model('Movie', movieSchema);
await Movie.createSearchIndexes(); // Create the index
Optionally, you can set dynamic: true to index all supported fields. However, this is not recommended in production as it can lead to unnecessary storage usage.
For more control over how fields are indexed, use custom the analyzer option.
Atlas Search uses Apache Lucene analyzers for text processing. Analyzers determine how text is tokenized, filtered, and indexed. Common analyzers include:
lucene.standard - General-purpose text analysis (tokenizes on whitespace and punctuation)lucene.english - English language analysis with stemminglucene.keyword - Treats entire field value as a single token (exact matching)For a complete list of analyzers and their configurations, see the MongoDB Atlas Search Analyzers documentation.
movieSchema.searchIndex({
name: 'movie_search',
definition: {
mappings: {
dynamic: false,
fields: {
title: {
type: 'string',
analyzer: 'lucene.standard' // Tokenize on whitespace/punctuation
},
fullplot: {
type: 'string',
analyzer: 'lucene.english' // English language analysis with stemming
},
genres: {
type: 'string',
analyzer: 'lucene.keyword' // Exact match only (no tokenization)
},
cast: {
type: 'string',
analyzer: 'lucene.standard'
},
year: {
type: 'number'
}
}
}
}
});
Mongoose provides several methods for managing Atlas Search indexes:
// Create all indexes defined in the schema
await Movie.createSearchIndexes();
// Create a single index programmatically
await Movie.createSearchIndex({
name: 'my_index',
definition: {
mappings: { dynamic: true }
}
});
const indexes = await Movie.listSearchIndexes();
for (const index of indexes) {
console.log(`${index.name}: ${index.status}`);
}
await Movie.updateSearchIndex('movie_search', {
mappings: {
dynamic: false,
fields: {
title: { type: 'string' },
fullplot: { type: 'string' },
cast: { type: 'string' },
year: { type: 'number' }
}
}
});
await Movie.dropSearchIndex('old_index');
Once your search index is created, you can use the $search aggregation stage to perform text searches.
// Example query showcasing different text search options:
const results = await Movie.aggregate([
{
$search: {
index: 'movie_search',
text: {
query: 'eternal sunshine',
path: 'title' // Single field search
// path: ['title', 'fullplot', 'genres'] // Multi-field: search across multiple fields
// fuzzy: { maxEdits: 2 } // Fuzzy: tolerate typos (up to 2 char differences)
}
}
},
{ $limit: 10 }
]);
Combine multiple search criteria with must, should, and filter clauses. Include relevance scores using the $meta operator. Atlas Search scores are relative to your dataset, so remember to adjust the $match threshold based on the scores you observe in your data.
// Find movies whose title includes 'mission' released since 2000, ranked by relevance,
// with a score boost for movies whose cast includes Tom Cruise.
// Top 3 results should be `Mission: Impossible II`, `Mission: Impossible - Ghost Protocol`,
// and `Mission: Impossible III`.
const results = await Movie.aggregate([
{
$search: {
index: 'movie_search',
compound: {
must: [
{
text: {
query: 'mission',
path: 'title'
}
}
],
should: [
{
text: {
query: 'tom cruise',
path: 'cast',
score: { boost: { value: 5 } }, // Double the score for movies starring Tom Cruise
matchCriteria: 'all' // Only boost score if all terms match
}
}
],
filter: [
{
range: {
path: 'year',
gte: 2000 // Only include movies released in 2000 or later
}
}
]
}
}
},
{
$project: {
title: 1,
cast:1,
fullplot: 1,
score: { $meta: 'searchScore' } // Include the relevance score in the results
}
},
{
$match: {
score: { $gte: .5 } // Start low and adjust this threshold based on your data
}
}
]);
For semantic search using vector embeddings, use the $vectorSearch stage.
See the complete Vector Search guide for detailed examples.
Combine text search and vector search to leverage both keyword relevance and semantic similarity.
Use $rankFusion to run $vectorSearch and $search as separate subpipelines and merge their results using Reciprocal Rank Fusion (RRF). Note that $search must be the first stage in its subpipeline, which is why it cannot be used directly after $vectorSearch in the same pipeline.
This example uses the generateEmbedding() function from the Vector Search guide.
// See the Vector Search guide for details on generating embeddings
const queryEmbedding = await generateEmbedding('charming animals with adventurous tone');
const results = await Movie.aggregate([
{
$rankFusion: {
input: {
pipelines: {
// Semantic search subpipeline
vector: [
{
$vectorSearch: {
index: 'vector_index', // Name of your vector search index
path: 'plot_embedding_voyage_3_large', // Name of the field containing the embeddings
queryVector: queryEmbedding,
numCandidates: 100,
limit: 50
}
}
],
// Keyword search subpipeline
text: [
{
$search: {
index: 'movie_search',
text: { query: 'adventure animals', path: 'fullplot' }
}
},
{ $limit: 50 }
]
}
},
combination: {
weights: {
vector: 0.7, // 70% weight to semantic relevance
text: 0.3 // 30% weight to keyword relevance
}
}
}
},
{ $limit: 10 }
]);
For more details, see the Atlas Hybrid Search documentation.
autoSearchIndex: true in development: Automatically create indexes with your schemaModel.createSearchIndexes(), Atlas UI, MongoDB CLI, or deployment scripts to avoid unintended changes during application deploymentslistSearchIndexes() after creation to ensure indexes are ready (queryable: true)// Good: Define indexes in schema for version control
movieSchema.searchIndex({
name: 'movie_search',
definition: { mappings: { dynamic: false, fields: { /* ... */ } } }
});
// Also good: Separate index management for production
const createProductionIndexes = async () => {
await Article.createSearchIndex({ /* definition */ });
};
$limit early: Reduce the number of documents passed to subsequent pipeline stages$search must be the first stage: Place $search as the first stage in your pipeline — using $match before $search throws an error. To filter documents during search, use the filter clause inside a compound operator instead$project to return only necessary datadynamic: true in production. Dynamic mappings index every field. Use static mappings to index only the fields you searchFor production deployments, you may want to manage indexes through:
mongosh or MongoDB CLI tools for scripting index operationsDisable autoSearchIndex in production to prevent automatic index changes during deployments.