Use text indexes for search queries

Learn how to use MongoDB text indexes for fast, flexible search queries. This tutorial covers creating indexes, searching text fields, and handling edge cases.

Focus: use text indexes for search queries

Sponsored

You've built your collections, indexed for equality and range queries, but now users are typing free-form phrases into a search box and getting nothing back — or worse, your $regex queries are grinding the database to a halt. If you've ever tried to do a real search across a field and ended up with slow queries, painful manual tokenization, or results that completely miss the mark, you're feeling the pain this lesson solves. MongoDB's text indexes are the answer: they give you fast, multilingual, relevance-scored search across string fields with minimal setup. In this lesson, you'll learn how to create text indexes, run $text queries, and handle the tricky edges — so you can ship robust search without spinning up a separate search engine.

The problem this lesson solves

When your users search for "yellow banana," they don't expect an exact match on the whole phrase — they expect documents that mention yellow and banana, in any order, and maybe even slight variations like "bananas." A standard equality query can't do that. A $regex query like { description: /yellow banana/i } can, technically, but it's a full collection scan in most cases. As your collection grows from thousands to millions of documents, that approach becomes painfully slow and can saturate your CPU and I/O.

Here's the real pain:

  • Slow performance: $regex with leading wildcards can't use standard B-tree indexes, leading to full scans.
  • Inflexible matching: Users can't search for words in any order or get partial matches.
  • No relevance ranking: Even if results return, they're often not ordered by how well they match.
  • Manual work: Without a text index, you'd have to split strings, create arrays, and maintain them yourself — a recipe for bugs.

Text indexes eliminate this pain by pre-processing your string fields into searchable terms and supporting fast, ranked queries.

Core concept / mental model

Think of a text index as a specialized inverted index — like a book's index at the back, but for your entire collection. The index scans every document, breaks text into tokens (words), applies language-specific stemming (so "running" matches "run"), and stores a map from each term to the documents that contain it. When you query with $text, MongoDB uses that map to find document IDs instantly, scores each match by relevance (using TF-IDF-like algorithms), and returns results ordered by that score.

Key definitions:

  • Text index: A special index type that supports $text queries on string fields.
  • Stemming: Reducing words to their root form (e.g., "jumping" → "jump").
  • Stop words: Common words like "the," "a," "and" that are ignored because they add little search value (unless you specify none for the language).
  • $text operator: The query operator that tells MongoDB to use the text index.

A mental picture: your documents are like books on a shelf. A text index is the index at the back — you don't flip through every page; you look up the term and jump straight to the pages. That's why text queries are so fast.

How it works step by step

Creating a text index and querying it follows a predictable path. Here's the step-by-step flow:

  1. Create the text index on one or more string fields using createIndex with the text type.
  2. MongoDB parses each document's text: it lowercases, removes stop words, and stems tokens.
  3. The index stores term-to-document mappings in a compact structure.
  4. You run a $text query with a search string and optional options like language and case sensitivity.
  5. MongoDB scores documents based on term frequency and inverse document frequency.
  6. Results come back — typically sorted by descending score.

Each text index can cover multiple fields, effectively a compound text index (though you can't mix text with other index types in a single compound index). You can also create a wildcard text index to cover all string fields in a document.

Hands-on walkthrough

Let's put this into practice. We'll use a simple products collection. First, insert some sample documents:

from pymongo import MongoClient

client = MongoClient('mongodb://localhost:27017')
db = client['shop']
products = db['products']

products.insert_many([
    {'name': 'Banana', 'description': 'Fresh yellow banana, rich in potassium.'},
    {'name': 'Apple', 'description': 'Crisp red apple with a sweet flavor.'},
    {'name': 'Banana Chips', 'description': 'Dried banana slices, perfect for snacks.'},
    {'name': 'Green Grapes', 'description': 'Seedless green grapes, juicy and sweet.'}
])

Now create a text index on the description field:

products.create_index([('description', 'text')])

Now run a search for "banana":

results = products.find({'$text': {'$search': 'banana'}})
for doc in results:
    print(doc['name'], '-', doc['description'])

Expected output (order may vary):

Banana - Fresh yellow banana, rich in potassium.
Banana Chips - Dried banana slices, perfect for snacks.

Notice that both documents containing "banana" returned, even though one had it as a whole word and the other just as part of a phrase. The $text search is looking for the stemmed term anywhere in the field.

Now try a multi-word search with flexible ordering:

results = products.find({'$text': {'$search': 'yellow banana'}})
for doc in results:
    print(doc['name'], '-', doc['description'])

Expected output:

Banana - Fresh yellow banana, rich in potassium.
Banana Chips - Dried banana slices, perfect for snacks.

The second document matches because it contains "banana" but not "yellow" — MongoDB matches documents containing any of the terms by default. To require all terms, you must escape with double quotes.

Search for the exact phrase "yellow banana":

results = products.find({'$text': {'$search': '\"yellow banana\"'}})
for doc in results:
    print(doc['name'], '-', doc['description'])

Expected output:

Banana - Fresh yellow banana, rich in potassium.

Only the exact phrase appears. That's the power of phrase searches — wrap terms in escaped double quotes.

Now add relevance ranking:

results = products.find(
    {'$text': {'$search': 'banana'}},
    {'score': {'$meta': 'textScore'}}
).sort([('score', {'$meta': 'textScore'})])

for doc in results:
    print(doc['name'], '-', doc['score'])

Expected output (scores may vary):

Banana - 1.0
Banana Chips - 0.8

The document where the term appears in the name field (if indexed) or more prominently ranks higher. If you indexed only description, both documents have the term there, and the score may tie.

Notice: With $text, you can't use a compound index with other types on the same fields — it's text-only.

Compare options / when to choose what

Now that you've seen text indexes in action, compare them with alternatives:

Approach Pros Cons When to use
Text index + $text Fast, relevance score, stemming, multi-language, easy setup No fuzzy matching, limited to string fields, index size General search within a single collection
$regex alone Flexible patterns, no special index Full scans, slow on large data, no relevance ranking Small collections or exact pattern needs
Third-party search (Elasticsearch, Atlas Search) Advanced features: typo tolerance, synonyms, faceting External service, complexity Large-scale, full-text search across many fields or enterprise needs
Array of keywords manually You control tokenization Duplicate data, maintenance overhead Edge cases where stemming must be disabled completely

When to choose text indexes: - Your search needs are simple phrase/keyword matching. - You need relevance ranking out of the box. - Single collection search is sufficient.

When to defer to a full search engine: - You need fuzzy matching (typos) or synonym expansion. - You need to search across multiple collections simultaneously. - You need advanced analytics on search terms.

Troubleshooting & edge cases

"Text index not found" error

If you run a $text query without creating an index, MongoDB raises Error: $text requires a text index. Always create_index before querying.

Index not used due to wrong field

If your index covers description but you search on name, the query fails. Create a compound text index covering both fields:

products.create_index([('name', 'text'), ('description', 'text')])

Stop words breaking results

The default language removes stop words. Searching for "the" or "a" returns nothing. To disable stop words, set language to 'none' in the index creation:

products.create_index([('description', 'text')], default_language='none')

Negative searches aren't supported directly

$text can't exclude terms. Use $text for positive matches and combine with $nin or $regex for exclusions.

Wildcard search not working

Text indexes don't support wildcard * inside $search — pattern matching is not like LIKE. Use $regex if you need prefixes.

Case sensitivity

By default, text indexes are case-insensitive for Latin scripts. If you need case sensitivity, set caseSensitive: true in the $text query.

What you learned & what's next

You've mastered the key points: - Understand the concept: Text indexes are inverted indexes that tokenize, stem, and store term mappings for fast $text queries. - Applied it: You created a text index, ran single-word, multi-word, and phrase searches, and ranked results by relevance score. - Connected to next: You're now ready to combine text indexes with other query operators, aggregation pipelines, or even move to Atlas Search for more advanced full-text capabilities.

What's next in this track

In the next lesson, you'll explore compound indexes — how to combine multiple fields and index types to optimize complex queries beyond text search. That builds directly on the index fundamentals you've learned here, helping you design indexes for real-world workloads.

Pro tip: Remember, text indexes are just one tool in your indexing toolbox. They're perfect for search-heavy scenarios, but don't use them for every query — regular indexes are still faster for exact matches.

Now go ahead and practice! Try adding a weight to your index fields to boost one field's relevance over another, or index a new collection with multiple languages and see how $text handles stemming in different languages.

Practice recap

Create a new collection articles with fields title and body. Insert at least 5 realistic articles, then create a text index on both fields. Run searches for single keywords, multi-word terms, and a quoted phrase. Also try adding weights to boost the title field and see how the scores change. Finally, test a case with default_language: 'none' to see how stop words affect results.

Common mistakes

  • Forgetting to create a text index before using $text, causing a runtime error every time.
  • Using $text on a field that isn't covered by the index — the query fails or falls back to slow regex.
  • Assuming $text supports partial or wildcard matching; it's for exact terms and phrases, not prefix patterns.
  • Ignoring stop words: searching for common words like 'the' returns zero results in default languages.

Variations

  1. Use a wildcard text index to cover all string fields in a document without specifying each field name.
  2. Set custom weights per field to influence relevance scoring (e.g., name > description).
  3. Integrate MongoDB Atlas Search for more advanced features like fuzzy matching and synonyms, but note it's an external add-on.

Real-world use cases

  • E-commerce product search: users type 'wireless mouse' and get products with 'wireless' or 'mouse' in name or description, ranked by relevance.
  • Support knowledge base: searchable FAQ articles where queries like 'password reset' return docs containing either term, sorted by match quality.
  • Blog/news search: find posts by keywords across title and body, with phrase search for exact quotes.

Key takeaways

  • Text indexes enable fast, relevance-scored search over string fields using the $text operator.
  • Create text indexes with createIndex([('field', 'text')]) before querying.
  • Use escaped double quotes for exact phrase matching; otherwise, terms are OR-ed by default.
  • Text indexes support stemming, stop words, and multiple languages out of the box.
  • Text indexes don't support partial/fuzzy matching — use $regex or third-party search for that.
  • Measure index size and update performance when adding text indexes to large collections.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.