MongoDB Search supports multiple index types, field mapping types, analyzers, and search operators ($search, $vectorSearch, and $rankFusion). Refer to this page when you write search queries or tune search performance.
Overview
MongoDB Search integrates dedicated search nodes (mongot) into a MongoDB instance to provide full-text search and vector search capabilities tightly coupled with the database. You can run multimodal search tasks through the MongoDB Query Language (MQL) without managing an external search system.
For the scope, architecture, activation, and node management of MongoDB Search, see MongoDB Search. This topic focuses on algorithm parameters and operators for defining indexes and running search queries.
Search index types
Full-text search indexes
Use the createSearchIndex method to create a full-text search index for use with the $search aggregation stage. You can map fields dynamically or statically.
Dynamic mapping
Set dynamic: true to automatically index all fields in the collection. This option is suitable for rapid prototyping.
db.reviews.createSearchIndex({
name: "reviews_full_text_index",
definition: {
mappings: {
dynamic: true
}
}
});
Static mapping
Specify the fields to index and their types. This approach is suitable for production environments because it reduces storage overhead by limiting the index scope.
db.movies.createSearchIndex({
name: "movies_custom_index",
definition: {
mappings: {
dynamic: false,
fields: {
title: { type: "string", analyzer: "lucene.standard" },
genre: { type: "stringFacet" },
year: { type: "number" },
rating: { type: "number" },
plot: { type: "string", analyzer: "lucene.standard" }
}
}
}
});
Vector search indexes
Use the three-argument form of createSearchIndex with the type set to "vectorSearch" to create a vector search index for use with the $vectorSearch aggregation stage.
db.images.createSearchIndex(
"vector_index", // Index name.
"vectorSearch", // Index type.
{
fields: [
{
type: "vector",
path: "embedding", // Field that stores the vector.
numDimensions: 1536, // Number of dimensions in the vector.
similarity: "dotProduct", // Similarity metric.
quantization: "scalar" // Quantization method (optional).
}
]
}
);
Field mapping types
In the fields object of a static mapping index, specify the type for each field:
|
Type |
Description |
Use case |
|
|
Analyzed string field for full-text indexing. |
Titles, descriptions, comments, and other text that requires full-text search. |
|
|
Unanalyzed string field used for facet aggregations. |
Categories, tags, and other fields used to group documents. |
|
|
Numeric types (int, long, float, and double). |
Range queries on numeric fields such as price, rating, and year. |
|
|
Date type. |
Time range queries. |
|
|
Boolean type. |
Status filtering. |
|
|
ObjectId type. |
_id or reference fields. |
|
|
Geographic coordinates (GeoJSON). |
Geospatial search. |
|
|
Unanalyzed string for exact matching. |
Exact matches on enumerated values, IDs, and SKUs. |
|
|
Autocomplete type. |
Search suggestions and prefix matching. |
|
|
Embedded document. |
Indexing nested object fields. |
|
|
Vector type (only for vector search indexes). |
Vector similarity search. |
Analyzers
An analyzer controls how text is tokenized and normalized. Specify an analyzer for a string field by setting the analyzer parameter.
Built-in analyzers
|
Analyzer |
Description |
Tokenization example |
|
|
Standard analyzer. Tokenizes text by Unicode text segmentation rules and converts to lowercase. |
"The Quick Fox" → ["the", "quick", "fox"] |
|
|
Splits on non-letter characters and converts to lowercase. |
"hello-world 123" → ["hello", "world"] |
|
|
Splits on whitespace only. Does not convert to lowercase. |
"Hello World" → ["Hello", "World"] |
|
|
Treats the entire field value as a single token. |
"New York" → ["New York"] |
|
|
English analyzer with stemming and stop word filtering. |
"running quickly" → ["run", "quick"] |
|
|
Chinese analyzer with Chinese word segmentation. |
"全文搜索引擎" → ["全文", "搜索", "引擎"] |
Custom analyzers
Build a custom analyzer by combining a tokenizer, token filters, and character filters:
db.collection.createSearchIndex({
name: "custom_analyzer_index",
definition: {
mappings: {
dynamic: false,
fields: {
content: {
type: "string",
analyzer: "myCustomAnalyzer"
}
}
},
analyzers: [
{
name: "myCustomAnalyzer",
charFilters: [],
tokenizer: {
type: "standard"
},
tokenFilters: [
{ type: "lowercase" },
{ type: "stopword", tokens: ["the", "a", "an", "is"] }
]
}
]
}
});
Tokenizer types
|
Tokenizer |
Description |
|
|
Tokenizes text by Unicode text segmentation rules. |
|
|
Splits on whitespace. |
|
|
Treats the entire input as a single token. |
|
|
Tokenizes by regex capture groups. |
|
|
Splits by regex delimiter. |
|
|
N-gram tokenization. |
|
|
Edge N-gram (prefix tokenization). |
|
|
Recognizes and preserves URLs and email addresses. |
Token filters
|
Filter |
Description |
|
|
Converts to lowercase. |
|
|
Removes stop words. |
|
|
Reduces tokens to their root form. |
|
|
Expands tokens with synonyms. |
|
|
Generates N-gram phrases. |
|
|
Trims leading and trailing whitespace. |
|
|
Filters tokens by length. |
|
|
Unicode folding (removes diacritics and similar marks). |
|
|
Unicode normalization. |
Full-text search operators ($search)
Run full-text search queries by using the $search aggregation stage. The following operators are supported.
text
Performs a basic full-text search. The query text is tokenized and matched against the target field.
db.reviews.aggregate([
{
$search: {
index: "reviews_full_text_index",
text: {
query: "good quality", // Query keywords.
path: "comment" // Search in the comment field.
}
}
},
{ $limit: 5 },
{ $project: { _id: 0, comment: 1, score: { $meta: "searchScore" } } }
]);
Parameters:
-
query: The query keywords. Can be a string or an array of strings. -
path: The field or fields to search. Can be a string or an array of strings. -
fuzzy: Fuzzy matching configuration. Optional. -
score: Score modifier. Optional. -
synonyms: Name of a synonym mapping. Optional.
compound
Combines multiple search clauses by using Boolean logic.
db.movies.aggregate([
{
$search: {
index: "movies_custom_index",
compound: {
must: [
{ text: { query: "Drama", path: "genre" } }
],
mustNot: [
{ text: { query: "horror", path: "genre" } }
],
should: [
{ text: { query: "award", path: "plot" } }
],
filter: [
{ range: { path: "year", gte: 2000, lte: 2023 } }
]
}
}
},
{ $project: { title: 1, genre: 1, year: 1, _id: 0, score: { $meta: "searchScore" } } }
]);
Clauses:
|
Clause |
Meaning |
Affects score |
|
|
Documents must match all clauses. |
Yes |
|
|
Documents must not match any of these clauses. |
No |
|
|
Matching documents receive a higher score. |
Yes |
|
|
Documents must match, but the clause does not contribute to the score. |
No (better performance) |
phrase
Searches for terms that appear in order. Use the slop parameter to allow gaps between terms.
db.movies.aggregate([
{
$search: {
index: "default",
phrase: {
query: "organized crime",
path: "plot",
slop: 0 // Allowed gap between terms (default 0, strict adjacency).
}
}
},
{ $project: { title: 1, _id: 0, score: { $meta: "searchScore" } } }
]);
// Matches The Godfather (the plot contains the phrase "organized crime").
A slop value greater than 0 allows other terms between matched terms:
// slop: 5 allows up to 5 terms between "men" and "bond".
{ phrase: { query: "men bond", path: "plot", slop: 5 } }
// Matches The Shawshank Redemption ("men bond over a number of years").
range
Performs a range query on numeric or date fields.
{
$search: {
range: {
path: "rating",
gte: 8.5,
lt: 10
}
}
}
Supported comparisons: gt (greater than), gte (greater than or equal to), lt (less than), lte (less than or equal to).
fuzzy (fuzzy matching)
Enabled within the text operator by setting the fuzzy parameter. Tolerates typos in queries.
db.movies.aggregate([
{
$search: {
index: "default",
text: {
query: "Godfater", // Typo.
path: "title",
fuzzy: {
maxEdits: 1, // Maximum edit distance (1 or 2).
prefixLength: 2, // Number of leading characters that must match exactly.
maxExpansions: 50 // Maximum number of expansion variants.
}
}
}
},
{ $project: { title: 1, _id: 0, score: { $meta: "searchScore" } } }
]);
// Matches "The Godfather".
wildcard
Uses * (matches zero or more characters) and ? (matches a single character) for pattern matching. Matching occurs at the token level after analysis.
db.movies.aggregate([
{
$search: {
index: "default",
wildcard: {
query: "inter*",
path: "title",
allowAnalyzedField: true
}
}
},
{ $project: { title: 1, _id: 0, score: { $meta: "searchScore" } } }
]);
// Matches Interstellar (the "interstellar" token matches "inter*").
Parameters:
-
query: Wildcard pattern (*matches zero or more characters;?matches a single character). -
path: The field to search. -
allowAnalyzedField: Must be set totruewhen targeting analyzed fields.
regex
Uses a regular expression for pattern matching. Based on the Lucene regex engine. Matching occurs at the token level after analysis.
db.movies.aggregate([
{
$search: {
index: "default",
regex: {
query: ".*tion",
path: "plot",
allowAnalyzedField: true
}
}
},
{ $project: { title: 1, _id: 0, score: { $meta: "searchScore" } } },
{ $limit: 5 }
]);
// Matches documents whose plot contains tokens ending in "tion" (such as redemption or fiction).
Parameters:
-
query: Regex pattern (supports.,*,+,?,|,(),[ ],{n,m}, and similar syntax). -
path: The field to search. -
allowAnalyzedField: Must be set totruewhen targeting analyzed fields.
autocomplete
Works with the autocomplete field type to support prefix matching and search suggestions.
// Index definition: configure the autocomplete type for the title field.
db.movies.createSearchIndex({
name: "ac_index",
definition: {
mappings: {
dynamic: false,
fields: {
title: [
{ type: "autocomplete" },
{ type: "string", analyzer: "lucene.standard" }
]
}
}
}
});
// Query: autocomplete on the input "inter".
db.movies.aggregate([
{
$search: {
index: "ac_index",
autocomplete: {
query: "inter",
path: "title"
}
}
},
{ $project: { title: 1, _id: 0, score: { $meta: "searchScore" } } }
]);
// Matches Interstellar.
exists
Checks whether a specified field exists in a document.
db.movies.aggregate([
{
$search: {
index: "default",
exists: {
path: "rating"
}
}
},
{ $project: { title: 1, rating: 1, _id: 0, score: { $meta: "searchScore" } } },
{ $limit: 3 }
]);
Vector search ($vectorSearch)
Similarity metrics
When you create a vector search index, set the similarity parameter to specify the similarity metric:
|
Metric |
Description |
Use case |
|
|
Euclidean distance. Smaller values indicate higher similarity. |
Absolute distance in a feature vector space. |
|
|
Cosine similarity. Larger values indicate higher similarity. |
Text embeddings and semantic search. Most common. |
|
|
Dot product. Larger values indicate higher similarity. |
Normalized vectors. Offers better performance than cosine. |
Quantization
Specify a quantization method with the quantization parameter to reduce memory usage:
|
Method |
Description |
|
|
Scalar quantization. Maps floating-point values to integers to reduce storage and memory usage. |
|
Not specified |
Uses the original floating-point precision. |
Vector search query
// Assume that QUERY_EMBEDDING is a vector generated by an AI model.
const QUERY_EMBEDDING = [0.12, 0.45, -0.23, ...];
db.images.aggregate([
{
$vectorSearch: {
index: "vector_index",
path: "embedding",
queryVector: QUERY_EMBEDDING,
numCandidates: 150, // Number of candidates (trade-off between performance and recall).
limit: 10 // Number of results to return.
}
},
{
$project: {
_id: 0,
title: 1,
score: { $meta: "vectorSearchScore" } // Vector search relevance score.
}
}
]);
Parameters:
|
Parameter |
Type |
Description |
|
|
string |
Name of the vector search index. |
|
|
string |
Path of the vector field. |
|
|
array |
Query vector. The number of dimensions must match the index definition. |
|
|
int |
Number of candidates. A larger value improves recall but consumes more resources. |
|
|
int |
Number of nearest-neighbor results to return. |
|
|
string |
Quantization method to use at query time. Optional. |
Hybrid search ($rankFusion)
To use $rankFusion, go to Parameter Settings and set both setParameter.featureFlagRankFusionBasic and setParameter.featureFlagRankFusionFull to true. The instance restarts automatically for the change to take effect.
The $rankFusion aggregation stage merges results from multiple search pipelines by using the Reciprocal Rank Fusion (RRF) algorithm. It is designed for hybrid scenarios that combine full-text search and vector search.
RRF algorithm
RRF calculates a fused score based on the ranking of each document in each subpipeline:
score = weight × (1 / (rank + 60))
Where rank is the document's ranking in the subpipeline result (starting from 1), and 60 is the RRF smoothing constant. When a document appears in multiple subpipelines, the final score is the sum of its scores from each subpipeline.
Syntax and parameters
db.collection.aggregate([
{
$rankFusion: {
input: {
pipelines: {
"<pipeline_name_1>": [ /* Subpipeline 1 */ ],
"<pipeline_name_2>": [ /* Subpipeline 2 */ ]
}
},
combination: { // Optional.
weights: {
"<pipeline_name_1>": number,
"<pipeline_name_2>": number
}
},
scoreDetails: boolean // Optional. Default: false.
}
}
]);
Parameters:
|
Parameter |
Type |
Required |
Description |
|
|
object |
Yes |
Named subpipelines. Each subpipeline must be an "ordered pipeline" (starts with |
|
|
object |
No |
Weight map for each subpipeline. Unspecified pipelines default to a weight of 1. |
|
|
boolean |
No |
Whether to include detailed per-pipeline scores in the result. Default: |
Examples
Hybrid full-text and vector search
db.products.aggregate([
{
$rankFusion: {
input: {
pipelines: {
fullTextSearch: [
{ $search: { index: "default", text: { query: "wireless headphones", path: "description" } } },
{ $limit: 20 }
],
vectorSearch: [
{ $vectorSearch: { index: "vector_index", path: "embedding", queryVector: QUERY_VECTOR, numCandidates: 100, limit: 20 } }
]
}
},
combination: {
weights: {
fullTextSearch: 0.3,
vectorSearch: 0.7
}
}
}
},
{ $limit: 10 },
{ $project: { title: 1, score: { $meta: "searchScore" } } }
]);
View detailed scores
db.products.aggregate([
{
$rankFusion: {
input: {
pipelines: {
textSearch: [
{ $search: { index: "default", text: { query: "laptop", path: "title" } } },
{ $limit: 10 }
],
ratingSort: [
{ $sort: { rating: -1 } },
{ $limit: 10 }
]
}
},
scoreDetails: true
}
},
{ $limit: 5 },
{ $project: { title: 1, score: { $meta: "searchScore" }, details: { $meta: "scoreDetails" } } }
]);
Subpipeline requirements
Each subpipeline must be an "ordered pipeline" that satisfies one of the following conditions:
-
Starts with a
$searchstage. -
Starts with a
$vectorSearchstage. -
Contains a
$sortstage.
Usage notes
-
We recommend that you control the number of results in each subpipeline with
$limitto avoid processing too many candidate documents. -
Pipelines that are not specified in
weightsdefault to a weight of 1. All weight values must be positive numbers. -
scoreDetails: trueincludes per-pipeline rankings and scores in the result, which is useful for debugging and evaluating the fusion behavior.
scoreDetails output format
When scoreDetails: true, the structure of the details retrieved by { $meta: "scoreDetails" } is as follows:
// scoreDetails output example.
{
value: 0.03252, // Final RRF fusion score.
description: "value output by reciprocal rank fusion algorithm, computed as sum of (weight * (1 / (60 + rank))) across input pipelines...",
details: [
{
inputPipelineName: "textSearch", // Subpipeline name.
rank: 1, // Document ranking in this pipeline (starts at 1).
weight: 1, // Pipeline weight.
value: 0.6887, // Original score from this pipeline.
details: []
},
{
inputPipelineName: "vectorSearch",
rank: 2,
weight: 1,
value: 0.9999,
details: []
}
]
}
// Final score: 1 × (1/(60+1)) + 1 × (1/(60+2)) = 0.01639 + 0.01613 = 0.03252
Scoring and result processing
Retrieving the relevance score
// Full-text search score.
{ $project: { score: { $meta: "searchScore" } } }
// Vector search score.
{ $project: { score: { $meta: "vectorSearchScore" } } }
Score modifiers
In full-text search operators, adjust the score by setting the score parameter:
// boost: Multiply the original score.
{ text: { query: "crime", path: "genre", score: { boost: { value: 5 } } } }
// Original score 0.575 → boosted score 2.874.
// constant: Set a fixed score value.
{ text: { query: "crime", path: "genre", score: { constant: { value: 10 } } } }
// All matching documents receive a score of 10.
Supported score modifiers:
-
boost: Multiplies the original score. -
constant: Sets a fixed score value. -
function: Computes the score by a custom function that can reference field values dynamically.
Highlight
Returns context snippets around matching text. Useful for surfacing matched keywords in search results.
db.movies.aggregate([
{
$search: {
index: "default",
text: { query: "war", path: "plot" },
highlight: {
path: "plot",
maxCharsToExamine: 500,
maxNumPassages: 3
}
}
},
{
$project: {
title: 1,
_id: 0,
score: { $meta: "searchScore" },
highlights: { $meta: "searchHighlights" }
}
}
]);
storedSource
Stores original field values in the index so that they can be returned directly at query time without a collection lookup, reducing latency.
Index definition:
db.collection.createSearchIndex({
name: "stored_index",
definition: {
mappings: { dynamic: true },
storedSource: {
include: ["title", "genre"]
}
}
});
Query with returnStoredSource:
db.movies.aggregate([
{
$search: {
index: "stored_index",
text: { query: "drama", path: "genre" },
returnStoredSource: true
}
},
{ $project: { title: 1, genre: 1, _id: 0, score: { $meta: "searchScore" } } },
{ $limit: 3 }
]);
Facet aggregation ($searchMeta)
Use the $searchMeta aggregation stage to count documents by category, typically for faceted navigation in search results:
db.movies.aggregate([
{
$searchMeta: {
index: "movies_custom_index",
facet: {
operator: {
range: { path: "rating", gte: 8.0 }
},
facets: {
genreFacet: {
type: "string",
path: "genre",
numBuckets: 10
},
yearFacet: {
type: "number",
path: "year",
boundaries: [1990, 2000, 2010, 2020]
}
}
}
}
}
]);
Note: When you use $searchMeta, the genre field must be defined as stringFacet in the index.
Index management
|
Operation |
Method |
|
Create a full-text search index |
|
|
Create a vector search index |
|
|
List indexes |
|
|
List indexes (shorthand) |
|
|
Update an index definition |
|
|
Drop an index |
|
Index build and state
After you create an index, mongot builds it asynchronously in the background. The index lifecycle includes the following stages:
-
Index definition registration: After you call
createSearchIndex, the index definition is registered in themongotindex catalog immediately. At this point,$listSearchIndexesreturns the index (with theid,name,type, andlatestDefinitionfields). -
Initial sync:
mongotperforms a full sync frommongodand builds the index. This phase may take from several minutes to tens of minutes for large collections. -
Steady state: After initial sync, the index enters the steady state and can serve query requests normally.
How to confirm an index is ready:
-
$listSearchIndexesandgetSearchIndexes()return the index information immediately after creation, but this does not indicate that the index build has finished. The current response does not include a status field. -
The reliable way to confirm that the index is ready is to attempt a
$searchquery. If the index is not ready, the query returns an error. If the query succeeds, the index is available. -
You can also check the mongot log. When the message
Transitioning from INITIAL_SYNC to STEADY_STATEappears, the index has finished building and is queryable.
Index updates
When you update an index definition, mongot rebuilds the new index in the background. The old index continues to serve queries during the rebuild and is replaced transparently after the new index is ready.
Usage notes
-
Index build delay: An index is not queryable until its build finishes. Seeing the index in
$listSearchIndexesdoes not mean the index is ready. Confirm the index status by running a$searchquery or by checking the mongot log. -
Index updates: When you update an index definition,
mongotrebuilds the new index in the background. The old index continues to serve queries until the new one is ready and replaces it. -
Data sync latency: After data is written, it is typically searchable within seconds. The latency depends on factors such as the write load and document size.
-
Use static mapping in production: We recommend that you use static mapping in production to limit index scope and reduce storage overhead.
-
filter performance: The
filterclause in acompoundquery does not contribute to the score and performs better thanmust. Use it for exact filter conditions. -
Vector dimension consistency: The
numDimensionsof a vector search index must match the output dimension of the embedding model. -
Analyzer naming: Built-in analyzer names use the
lucene.prefix (for example,lucene.standard). -
wildcard and regex notes: When targeting analyzed (
string) fields, you must setallowAnalyzedField: true. Matching occurs at the token level rather than against the original text. -
Backup and recovery: An instance restored from a backup does not include Search indexes. You must activate the Search service again and rebuild all Search indexes on the new instance.
-
Data migration: When you migrate data with a tool such as DTS, Search indexes are not synchronized. You must recreate them manually on the target instance.