Text Search
Mongos built-in $text search covers tokenisation, stemming, stop words, language-aware indexing, and basic relevance scoring. For "good enough" search on a product catalog, blog index, or knowledge base it is faster to ship than Elasticsearch — but reach for Atlas Search / Elastic when you need facets, fuzzy matching, or large-scale ranking tuning.
Create a text index, query, score, and limit fields
EXAMPLE
// 1) Single-field text index
db.products.createIndex({ name: 'text' });
// Search documents and project a relevance score
db.products.find(
{ $text: { $search: 'wool jacket' } },
{ name: 1, price_cents: 1, score: { $meta: 'textScore' } }
).sort({ score: { $meta: 'textScore' } });
// 2) Multi-field text index with field weights
db.products.dropIndex('name_text');
db.products.createIndex(
{ name: 'text', description: 'text', tags: 'text' },
{
weights: { name: 10, tags: 5, description: 1 }, // bigger weight = bigger contribution
name: 'products_text_idx',
default_language: 'english',
},
);
// 3) Phrase + exclusion + boolean operators
db.products.find({ $text: { $search: '"linen shirt" -mens' } });
// '...' : phrase
// -word : exclude
// word1 word2 : OR by default; each word is a separate term
// 4) Language switch per query (e.g. a French catalog)
db.products.find({ $text: { $search: 'chemise lin', $language: 'french' } });
// 5) Score-only sort
db.products.find(
{ $text: { $search: 'wool jacket' } },
{ score: { $meta: 'textScore' }, name: 1 }
).sort({ score: { $meta: 'textScore' } }).limit(20);
// 6) Combine with non-text predicates (uses BOTH indexes when possible)
db.products.find({
$text: { $search: 'jacket' },
status: 'active',
price_cents: { $lte: 50000 },
});
// 7) Inside an aggregation pipeline — $facet for search results + facets
db.products.aggregate([
{ $match: { $text: { $search: 'wool jacket' }, status: 'active' } },
{ $facet: {
hits: [
{ $addFields: { score: { $meta: 'textScore' } } },
{ $sort: { score: { $meta: 'textScore' }, _id: 1 } },
{ $limit: 20 },
{ $project: { name: 1, price_cents: 1, score: 1 } },
],
total: [ { $count: 'value' } ],
categories: [
{ $group: { _id: '$category', count: { $sum: 1 } } },
{ $sort: { count: -1 } }, { $limit: 10 },
],
} },
]);
// 8) Inspect the planner
db.products.find({ $text: { $search: 'jacket' } }).explain('executionStats');
// Look for stage 'TEXT' or 'TEXT_OR'; rows examined should be small.
// 9) Limits and gotchas
// - Only ONE text index per collection (covers multiple fields).
// - Case- and diacritic-insensitive by default (good for product names).
// - Tokenisation is space + punctuation; CJK and other languages need
// external pipelines (or Atlas Search).
// - $text + $or with non-text predicates may not use the index efficiently;
// restructure into a pipeline ($match -> $text first).
// - No partial token / prefix matching out of the box; use Atlas Search.
// 10) When to graduate
// - Need typo tolerance / fuzziness -> Atlas Search (Lucene-backed)
// - Need synonyms / per-document boosts -> Atlas Search
// - Search index size > few GB -> Elastic / OpenSearch
// - Already heavy in OpenSearch -> stay there
Why it matters
$text is the right answer for "make this product / docs collection searchable in an afternoon". The moment you need typo tolerance, synonyms, faceted UI, or > a few GB of indexed text, move to Atlas Search or a dedicated engine; trying to push $text past its design space ends with brittle hacks that are harder to maintain than the migration would have been.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
db.posts.createIndex({ title: 'text', body: 'text' });
db.posts.find({ $text: { $search: 'mongo atlas' } });
Try it Yourself »
Discussion
Loading…