Almost every application has a search box. Users type "desk lamp" and expect to find products whose name or description mentions desks and lamps, with the best matches first, even if they typed "lamps" and the product says "lamp". A simple $regex query can't do this well: it matches exact character patterns, doesn't understand words, can't rank results, and on large collections it scans every document.
MongoDB's text indexes solve the basics. A text index breaks string fields into words, removes common words such as "the" and "and", and reduces words to their stem so "lamps" and "lamp" match. The $text query operator then searches the index, supports phrases and excluded words, and gives each result a relevance score you can sort by.
For more advanced needs, such as autocomplete, typo tolerance, synonyms and highlighting, MongoDB Atlas offers Atlas Search, built on Apache Lucene. In this lesson you'll create text indexes, write $text searches, sort by relevance, weight fields, compare with other techniques, and see when to step up to Atlas Search. The examples use this collection:
db.articles.insertMany([
{ _id: 1, title: 'Choosing a desk lamp', body: 'Bright lamps help you work at your desk without eye strain.' },
{ _id: 2, title: 'Kitchen lighting ideas', body: 'Pendant lamps and spotlights for cooking areas.' },
{ _id: 3, title: 'Desk organisation tips', body: 'Keep cables tidy and your desk clear.' },
{ _id: 4, title: 'Reading in bed', body: 'A warm bedside lamp is easier on the eyes than a ceiling light.' }
])Text indexes and $text
db.articles.createIndex({ title: 'text', body: 'text' })
db.articles.find({ $text: { $search: 'desk lamp' } })- A collection can have one text index, but it can cover several fields.
{ '$**': 'text' }indexes every string field. $searchwith several words matches documents containing any of them.- Searches are case-insensitive and use stemming: "lamps", "lamp" and "lamping" share a stem.
- Stop words such as "a", "the" and "and" are ignored.
Phrases, exclusions and relevance
| Search string | Meaning |
|---|---|
'desk lamp' | desk or lamp |
'"desk lamp"' | The exact phrase (with stemming), inside escaped quotes |
'lamp -kitchen' | lamp, but not kitchen |
Each match gets a text score based on how often and where the words appear. Project it with { score: { $meta: 'textScore' } } and sort by it with .sort({ score: { $meta: 'textScore' } }). Weights make some fields count more: createIndex({ title: 'text', body: 'text' }, { weights: { title: 5, body: 1 } }).
Other options
- Prefix search for autocomplete-style matches: an anchored, case-insensitive regex such as
{ name: { $regex: '^lam', $options: 'i' } }can use a normal index when combined with a case-insensitive collation index. - Exact matches ignoring case: create an index with
collation: { locale: 'en', strength: 2 }and query with the same collation. - Atlas Search adds autocomplete, fuzzy matching for typos, synonyms, highlighting, facets and better relevance through the
$searchaggregation stage. It's the right choice for a search experience users expect from modern sites.
MongoDB Performance Syntax
Search syntax
db.c.createIndex({ title: 'text', body: 'text' }, { weights: { title: 5 }, default_language: 'english' })
db.c.find({ $text: { $search: 'desk lamp' } })
db.c.find({ $text: { $search: '"desk lamp"' } })
db.c.find({ $text: { $search: 'lamp -kitchen' } })
db.c.find({ $text: { $search: 'lamp' } }, { score: { $meta: 'textScore' } }).sort({ score: { $meta: 'textScore' } })
db.c.find({ name: 'lamp' }).collation({ locale: 'en', strength: 2 })
db.c.aggregate([{ $search: { autocomplete: { query: 'lam', path: 'name' } } }]) // Atlas SearchMongoDB Performance Summary
- Text indexes break strings into words with stemming and stop words; a collection has at most one.
- $text: { $search } matches any word; quoted phrases and -words refine the search.
- Project and sort by { $meta: 'textScore' } to rank results by relevance.
- Weights make some fields, such as titles, count more.
- Use regex with an index for prefixes, collation for case-insensitive equality, and Atlas Search for advanced search.
MongoDB Performance Examples
Each example shows the code, the output it produces and what each part does.
Searching words, phrases and exclusions
db.articles.createIndex({ title: 'text', body: 'text' })
db.articles.find({ $text: { $search: 'lamps' } }, { _id: 1 }).sort({ _id: 1 })
db.articles.find({ $text: { $search: '"desk lamp"' } }, { _id: 1 })
db.articles.find({ $text: { $search: 'lamp -kitchen' } }, { _id: 1 }).sort({ _id: 1 })
db.articles.find({ $text: { $search: 'the and' } }).toArray().lengthtitle_text_body_text
[ { _id: 1 }, { _id: 2 }, { _id: 4 } ]
[ { _id: 1 } ]
[ { _id: 1 }, { _id: 4 } ]
0Line-by-line explanation
- Line 1One text index covers both title and body.
- Line 2Stemming lets "lamps" match "lamp" and "lamps" in any article.
- Line 3Escaped quotes search for the phrase "desk lamp".
- Line 4A minus sign excludes articles that mention kitchen.
- Line 5"the" and "and" are stop words, so they match nothing.
Ranking by relevance with weights
db.articles.createIndex({ title: 'text', body: 'text' }, { weights: { title: 5, body: 1 }, name: 'search' })
db.articles.find(
{ $text: { $search: 'desk lamp' } },
{ _id: 0, title: 1, score: { $meta: 'textScore' } }
).sort({ score: { $meta: 'textScore' } }).toArray().map(a => `${a.title} (${a.score.toFixed(2)})`)search
[
'Choosing a desk lamp (7.79)',
'Desk organisation tips (3.93)',
'Kitchen lighting ideas (0.60)',
'Reading in bed (0.57)'
]Line-by-line explanation
- Line 1Weights make words in the title count five times as much as words in the body.
- Lines 3 to 4$meta: 'textScore' adds the relevance score to each result.
- Line 4Sorting by the score puts the best match first.
- Line 4The exact scores depend on word counts and lengths; what matters is the order.
Case-insensitive and prefix matching
db.products.insertMany([{ name: 'Lamp' }, { name: 'lampshade' }, { name: 'LAMP' }, { name: 'Clamp' }])
db.products.createIndex({ name: 1 }, { collation: { locale: 'en', strength: 2 } })
db.products.find({ name: 'lamp' }, { _id: 0 }).collation({ locale: 'en', strength: 2 })
db.products.find({ name: { $regex: '^lamp', $options: 'i' } }, { _id: 0 })
db.products.find({ name: { $regex: 'lamp', $options: 'i' } }, { _id: 0 }).toArray().length{
acknowledged: true,
insertedIds: {
'0': ObjectId('...'),
'1': ObjectId('...'),
'2': ObjectId('...'),
'3': ObjectId('...')
}
}
name_1
[ { name: 'Lamp' }, { name: 'LAMP' } ]
[ { name: 'Lamp' }, { name: 'lampshade' }, { name: 'LAMP' } ]
4Line-by-line explanation
- Line 2An index with a case-insensitive collation (strength 2 ignores case).
- Line 3Querying with the same collation matches lamp in any case, exactly.
- Line 4An anchored regex finds names starting with lamp, in any case.
- Line 5Without the ^ anchor, Clamp also matches, and every document must be checked.
Common MongoDB Performance Mistakes
Unanchored regex for search on big collections
db.products.find({ description: { $regex: 'lamp', $options: 'i' } }) // scans every document, no rankingdb.products.createIndex({ name: 'text', description: 'text' })
db.products.find({ $text: { $search: 'lamp' } })Creating a second text index
db.articles.createIndex({ title: 'text' })
db.articles.createIndex({ body: 'text' }) // error: only one text index per collectiondb.articles.createIndex({ title: 'text', body: 'text' })Phrase search without escaped quotes
db.articles.find({ $text: { $search: 'desk lamp' } }) // any article with desk OR lampdb.articles.find({ $text: { $search: '"desk lamp"' } }) // the phraseMongoDB Performance in Real Projects
Search in real applications
Internal tools and small sites use text indexes for article and product search, sorted by score and limited to the top 20 results. Shops and marketplaces on Atlas use Atlas Search for autocomplete as users type, typo-tolerant fuzzy matching, synonyms such as "sofa" and "couch", and facets that count results by brand and price. Larger systems sometimes sync data to Elasticsearch or OpenSearch for the same features.
MongoDB Performance FAQ
Why doesn't MongoDB $text search find partial words?
A text index stores whole words, reduced to their stems, so searching for 'lam' doesn't match "lamp" or "lampshade". For prefix matching, such as autocomplete, use an anchored regex like /^lam/i on a normal field, or Atlas Search's autocomplete feature. Text search is designed for finding documents that contain given words, not for matching fragments.
Can a MongoDB collection have more than one text index?
No, each collection can have only one text index. If you need to search several fields, put them all in that one index, optionally with weights to rank some fields higher. To change which fields are covered, drop the existing text index and create a new one.
How do I sort MongoDB text search results by relevance?
Project the score and sort on it: db.articles.find({ $text: { $search: 'lamp' } }, { score: { $meta: 'textScore' } }).sort({ score: { $meta: 'textScore' } }). Documents that mention the words more often, or in more heavily weighted fields, come first. Add a limit so you return only the best matches.
Does MongoDB text search support languages other than English?
Yes. Set default_language when creating the index, such as { default_language: 'spanish' }, so stemming and stop words follow that language's rules; then 'casa' also matches "casas". Documents can override the language with a language field, and 'none' turns off stemming and stop words entirely. Some languages, such as Chinese and Japanese, aren't supported by the built-in text index.
What is the difference between $text and $regex in MongoDB?
$text uses a text index to find documents containing whole words, with stemming, stop words and a relevance score, and it can search several fields at once. $regex matches a character pattern in one field, including partial words, but only an anchored prefix pattern can use an index efficiently. Use $text for word search and $regex for codes, prefixes and exact patterns.
MongoDB Performance Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
Complete the index definition so title becomes searchable with $text.
db.posts.createIndex({ title: '' })
How many text indexes can a collection have?
What does $search: 'coffee -decaf' match?
How do you sort $text results by relevance?
Why does a search for "running" find documents containing "runs"?
MongoDB Performance Quiz
5 questions to check what you learned.
Which operator searches a text index?
- $regex
- $search in find
- $text
- $match
What are stop words?
- Words that end a search
- Banned words
- Words in quotes
- Very common words such as "the" and "and" that text indexes ignore
What do weights in a text index do?
- Make matches in some fields count more towards the score
- Limit results
- Sort by date
- Remove stop words
Which regex can use a normal index efficiently?
- /lamp/
- An anchored prefix such as /^lam/
- /.*lamp$/
- Any regex
Which feature needs Atlas Search rather than a basic text index?
- Matching any of several words
- Excluding words
- Autocomplete as you type with typo tolerance
- Case-insensitive search