Text Indexes
A text index enables full-text search over string content in a collection. Instead of matching exact values or using slow regex scans, a text index tokenizes strings into words, applies language-specific stemming (so running matches run), removes stop words (like the and a), and lets you search with the $text query operator.
Creating a Text Index
Use the special "text" index type instead of 1 or -1:
Single-field text index
// Index the title field for full-text search
db.articles.createIndex({ title: "text" })
// Now search it
db.articles.find({ $text: { $search: "mongodb indexes" } })Indexing Multiple Fields
A text index can cover several fields at once. A search then matches against all indexed fields:
Multi-field text index
db.articles.createIndex({
title: "text",
body: "text",
tags: "text"
})
// You can even index every string field in the document:
db.articles.createIndex({ "$**": "text" }) // wildcard text index$**) is convenient but indexes every string in every document, which can make the index very large and slow down writes. Prefer listing specific fields.Field Weights
By default every indexed field contributes equally to the relevance score. Use weights to make matches in some fields count more. Here a match in title is worth 10x a match in body:
Weighted text index
db.articles.createIndex(
{ title: "text", body: "text", tags: "text" },
{
weights: { title: 10, tags: 5, body: 1 },
name: "ArticleTextIndex"
}
)Querying with $text and $search
$text query syntax
// Match documents containing "mongodb" OR "atlas" (space = OR)
db.articles.find({ $text: { $search: "mongodb atlas" } })
// Exact phrase — wrap in escaped double quotes
db.articles.find({ $text: { $search: "\"replica set\"" } })
// Exclude a term with a leading minus
db.articles.find({ $text: { $search: "mongodb -mysql" } })
// Combine with regular filters
db.articles.find({
$text: { $search: "aggregation" },
status: "published",
views: { $gte: 100 }
})Sorting by Relevance with textScore
Every $text match gets a relevance score. Project it with $meta: "textScore" and sort on it to return the best matches first:
Relevance-sorted search
db.articles
.find(
{ $text: { $search: "mongodb performance" } },
{ title: 1, score: { $meta: "textScore" } }
)
.sort({ score: { $meta: "textScore" } })
.limit(5)[
{ _id: ObjectId('...'), title: 'MongoDB Performance Tuning', score: 3.2 },
{ _id: ObjectId('...'), title: 'Improving MongoDB Query Performance', score: 2.8 },
{ _id: ObjectId('...'), title: 'Performance Basics', score: 1.1 }
]Language Support
Text indexes support stemming and stop words for roughly 15 languages (English is the default). Set the language at index creation, or per document with a language field:
Language options
// Index-wide default language
db.articles.createIndex(
{ body: "text" },
{ default_language: "german" }
)
// Per-document language override — MongoDB reads the "language" field
db.articles.insertOne({
title: "Einführung in MongoDB",
body: "Dokumente werden in Sammlungen gespeichert...",
language: "german"
})
// Disable stemming and stop words entirely with "none"
db.logs.createIndex({ message: "text" }, { default_language: "none" })Limitations
One text index per collection. You cannot create a second one — combine all searchable fields into a single (optionally weighted) text index.
A query can include only one
$textexpression, and it cannot appear inside$elemMatchor in every position within$or.$textdoes no fuzzy matching — a typo like mongdb matches nothing.No partial-word (prefix/infix) matching: data will not match database.
Sorting on regular fields cannot use the text index; that sort runs in memory.
Text indexes can grow large (every unique stemmed word becomes an entry) and add write overhead.
Text Index vs. Atlas Search
Capability | Text Index ($text) | Atlas Search ($search) |
|---|---|---|
Engine | Built-in text index | Apache Lucene (managed) |
Availability | Any MongoDB deployment | MongoDB Atlas only |
Fuzzy matching / typo tolerance | No | Yes |
Autocomplete | No | Yes |
Relevance tuning | Field weights only | Rich scoring, boosting, functions |
Facets and highlighting | No | Yes |
Language analysis | Basic stemming, ~15 languages | 40+ analyzers, custom analyzers |
$text when you self-host or only need simple keyword matching without extra infrastructure.Complete Worked Example
Blog search end to end
db.posts.insertMany([
{ title: "Indexing Strategies", body: "Compound indexes follow the ESR rule...", tags: ["indexes"] },
{ title: "Aggregation Pipeline", body: "Stages transform documents step by step...", tags: ["aggregation"] },
{ title: "Index Internals", body: "B-trees keep entries sorted for fast range scans...", tags: ["indexes", "internals"] }
])
db.posts.createIndex(
{ title: "text", body: "text", tags: "text" },
{ weights: { title: 10, tags: 5, body: 1 }, name: "PostSearch" }
)
db.posts
.find(
{ $text: { $search: "index" } },
{ title: 1, _id: 0, score: { $meta: "textScore" } }
)
.sort({ score: { $meta: "textScore" } })[
{ title: 'Indexing Strategies', score: 10.5 },
{ title: 'Index Internals', score: 8.75 }
]Note how stemming made index, indexes, and indexing all match the single search term index, and how the title weight pushed "Indexing Strategies" to the top.
db.posts.getIndexes() and db.posts.dropIndex("PostSearch"). Naming your text index at creation time makes this much easier.