MongoDBText Search

Text Search

MongoDB's text search provides full-text search capability over string content. It tokenizes strings, ignores stop words, and supports stemming for language-aware searches. For advanced search requirements, MongoDB Atlas Search (built on Apache Lucene) offers far more power.

Creating a Text Index

Before using $text, you must create a text index on the field(s) you want to search. A collection can have at most one text index.

Creating text indexes

JS
// Single field text index
db.articles.createIndex({ title: "text" })

// Multiple fields — each gets a weight (default 1)
db.articles.createIndex(
  { title: "text", body: "text", tags: "text" },
  {
    weights: {
      title: 10,   // title matches rank higher
      body: 5,
      tags: 2
    },
    name: "ArticleTextIndex"
  }
)

// Wildcard text index — covers ALL string fields
db.articles.createIndex({ "$**": "text" })
Basic Text Search with $text

$text search examples

JS
// Search for documents containing "mongodb" OR "tutorial"
db.articles.find({ $text: { $search: "mongodb tutorial" } })

// Phrase search — wrap in escaped quotes
db.articles.find({ $text: { $search: '"mongodb tutorial"' } })

// Negation — exclude documents containing "beginner"
db.articles.find({ $text: { $search: "mongodb -beginner" } })

// Combine: must contain "mongodb", must NOT contain "deprecated"
db.articles.find({ $text: { $search: "mongodb -deprecated" } })
$text Operator Options

Option

Type

Description

$search

string

The search string — space-separated terms are OR'd; phrases use escaped quotes; prefix with - to negate

$language

string

Language for stop word removal and stemming (e.g. "english", "french", "german"). Default: "english"

$caseSensitive

boolean

Whether matching is case-sensitive. Default: false (case-insensitive)

$diacriticSensitive

boolean

Whether to distinguish accented characters (e.g. é vs e). Default: false

Relevance Score with $meta

MongoDB assigns a text relevance score to each matched document. Use { score: { $meta: "textScore" } } in projection to retrieve it, and sort by it for best-first ordering.

Sort by text relevance

JS
// Project the relevance score and sort best-first
db.articles.find(
  { $text: { $search: "mongodb indexing performance" } },
  { score: { $meta: "textScore" }, title: 1, _id: 0 }
).sort({ score: { $meta: "textScore" } })

// Example result:
// { title: "MongoDB Index Performance Deep Dive", score: 4.75 }
// { title: "Optimizing MongoDB Queries",          score: 3.20 }
// { title: "Introduction to MongoDB",             score: 1.10 }

// In an aggregation pipeline
db.articles.aggregate([
  { $match: { $text: { $search: "mongodb performance" } } },
  { $addFields: { score: { $meta: "textScore" } } },
  { $sort: { score: -1 } },
  { $limit: 10 },
  { $project: { title: 1, score: 1, _id: 0 } }
])
Phrase Search and Negation

Advanced $text syntax

JS
// Exact phrase — must appear as-is
db.articles.find({
  $text: { $search: '"web development"' }
})

// Exclude a term — results must NOT contain "mongodb"
db.articles.find({
  $text: { $search: "-mongodb" }
})

// Combine: exact phrase AND exclude a term
db.articles.find({
  $text: { $search: '"web development" -php' }
})

// Multiple phrases
db.articles.find({
  $text: { $search: '"node.js" "rest api" -deprecated' }
})
Language Support

MongoDB supports stop words and stemming for many languages. The default is English. Set $language in the query or on the index itself to override.

Language-specific search

JS
// Create a German text index
db.articles_de.createIndex(
  { title: "text", body: "text" },
  { default_language: "german" }
)

// Query with explicit language override
db.articles_de.find({
  $text: {
    $search: "datenbank performance",
    $language: "german"
  }
})

// Disable stop word removal by setting language to "none"
db.articles.find({
  $text: {
    $search: "the quick brown fox",
    $language: "none"  // "the" is now searchable
  }
})

// Supported languages include:
// "english", "french", "german", "spanish", "italian",
// "portuguese", "dutch", "russian", "chinese", "arabic", and more
Limitations of $text
  • Only one text index is allowed per collection — you cannot have two separate text indexes

  • No partial word / prefix matching — searching "mongo" does NOT match "mongodb" (use regex for that)

  • No fuzzy matching — typos return no results

  • No ranking customization beyond field weights set at index creation

  • No faceted search or aggregation-style filtering by category

  • $text cannot be used inside $or with other predicates at the same query level

  • Not available in $lookup pipelines

Atlas Search — The Better Alternative

For production full-text search, MongoDB Atlas Search (available on Atlas M10+ clusters) provides: fuzzy matching, autocomplete, highlighting, faceted navigation, synonyms, and custom scoring — all powered by Apache Lucene.

Atlas Search with $search

JS
// Atlas Search aggregation pipeline using $search stage
db.articles.aggregate([
  {
    $search: {
      index: "default",   // Atlas Search index name
      text: {
        query: "mongodb performance",
        path: ["title", "body"],
        // Fuzzy matching — tolerates typos
        fuzzy: {
          maxEdits: 1,      // 1 character difference allowed
          prefixLength: 3   // first 3 chars must match exactly
        }
      }
    }
  },
  {
    $project: {
      title: 1,
      score: { $meta: "searchScore" },
      highlights: { $meta: "searchHighlights" }
    }
  },
  { $sort: { score: -1 } },
  { $limit: 10 }
])

// Atlas Search autocomplete
db.articles.aggregate([
  {
    $search: {
      index: "autocomplete_index",
      autocomplete: {
        query: "mongo",        // user is typing "mongo..."
        path: "title",
        tokenOrder: "sequential"
      }
    }
  },
  { $limit: 5 },
  { $project: { title: 1, _id: 0 } }
])
Note
The $text operator can only be used in the first stage of an aggregation pipeline, and it cannot be combined with $or on a different field at the same query filter level. If you need to text-search AND filter on another field, add the additional filter as a separate query predicate alongside $text.
Tip
For simple keyword search in development or internal tools, $text is perfectly adequate. For production user-facing search — where users expect Google-like relevance, typo tolerance, and faceted filtering — migrate to Atlas Search early. Retrofitting it later is more work than starting with it.