4.5 Atlas Search: Search Index Definitions & $search Queries

Key Takeaways

  • Exam objectives 2.18 and 2.19 sit inside the 51% CRUD domain and test MongoDB Search (Atlas Search), not the MongoDB Query Language.
  • A search index is a Lucene inverted index maintained asynchronously by the mongot process; it is eventually consistent and is never used by the query planner for find().
  • db.collection.createSearchIndex() names the index 'default' when the name argument is omitted and defaults the type to 'search'; with dynamic: false, a mappings.fields object is required.
  • $search and $searchMeta must be the first stage of an aggregation pipeline, with the sole exception of the first stage inside a $lookup sub-pipeline in MongoDB 6.0 and later.
  • Inside compound, must and should contribute to the relevance score, mustNot excludes documents, and filter constrains results without affecting the score exposed via $meta: 'searchScore'.
Last updated: September 2026

4.5 Atlas Search: Search Index Definitions & $search Queries

The official MongoDB Associate Developer exam guide places two objectives inside the 51% CRUD domain that are not about the MongoDB Query Language at all: objective 2.18 asks you to identify the correct command for defining a search index, and objective 2.19 asks you to identify the correct search query. Both refer to MongoDB Search (historically branded Atlas Search) — a relevance-based, full-text search engine that runs alongside the database. Because these two objectives sit in the heaviest-weighted domain, skipping them is one of the most common ways well-prepared candidates lose points.


1. A Search Index Is Not a Database Index

This is the single most important distinction in this section, and the exam tests it directly.

A database index (what db.collection.createIndex() builds) is a B-tree keyed on field values. It is updated synchronously, inside the same write operation that modifies the document, and it is consumed by the query planner to turn a COLLSCAN into an IXSCAN.

A search index is an inverted index — a mapping from terms to the documents containing those terms — built on Apache Lucene. It is maintained by a separate process called mongot, which follows the collection's change stream and updates the Lucene index asynchronously. Two consequences follow directly:

  • Search indexes are eventually consistent. A document you just inserted may not appear in $search results for a short interval. You cannot rely on read-your-own-write behavior.
  • Search indexes are invisible to the query planner. Creating a search index will never speed up an ordinary find(), and explain() on a find() will never show it.
Database IndexSearch Index
StructureB-treeLucene inverted index
Created withcreateIndex()createSearchIndex()
Queried withfind(), $match$search, $searchMeta
Update timingSynchronous with the writeAsynchronous via mongot
Used by query plannerYes (IXSCAN)No
Relevance scoringNone{ $meta: "searchScore" }

2. Defining a Search Index (Objective 2.18)

In mongosh, search indexes are created with db.<collection>.createSearchIndex(), available from MongoDB 7.0 (and backported to 6.0.7). The full signature takes three arguments, two of which are optional:

db.<collection>.createSearchIndex(
  <name>,        // optional string
  <type>,        // optional: "search" (default) or "vectorSearch"
  { <definition> }  // required
)

Two defaults are directly examinable:

  • If you omit name, the index is named default.
  • If you omit type, the index type is search.

Dynamic Mappings

The simplest definition tells MongoDB Search to index every supported field type automatically:

db.movies.createSearchIndex({ mappings: { dynamic: true } })
// -> creates a search index literally named "default"

Static Mappings

Static mappings enumerate exactly which fields are indexed and how each is analyzed. This is the production-grade choice: smaller indexes, faster builds, predictable scoring.

db.movies.createSearchIndex(
  "movieTitleIdx",
  {
    mappings: {
      dynamic: false,
      fields: {
        title: { type: "string" },
        plot:  { type: "string" },
        year:  { type: "number" }
      }
    }
  }
)

Exam trap: when mappings.dynamic is false, mappings.fields is required. A definition of { mappings: { dynamic: false } } with no fields object indexes nothing, and every $search query against it returns zero results — with no error to tell you why.

Search indexes can also be created from the Atlas UI (Search tab → Create Search Index, using either the Visual Editor or the JSON Editor), the Atlas CLI, Compass, the Atlas Admin API, and the official drivers. The equivalent database command is createSearchIndexes. Companion helpers are $listSearchIndexes (an aggregation stage), db.<collection>.updateSearchIndex(), and db.<collection>.dropSearchIndex().

3. Writing a Search Query (Objective 2.19)

A search query is an aggregation pipeline stage, not a find() filter. MongoDB Search provides two stages:

  • $search — runs the full-text query and returns the matching documents, ordered by descending relevance.
  • $searchMeta — returns only metadata about the results (facet buckets, total counts) without returning the documents themselves.

The First-Stage Rule

$search and $searchMeta must be the first stage of the aggregation pipeline. Placing any other stage ahead of them is an error, not a performance problem. This is the most frequently tested fact about search queries:

// INVALID — $match precedes $search
db.movies.aggregate([
  { $match:  { year: { $gte: 2000 } } },
  { $search: { text: { query: "space", path: "plot" } } }
])

// VALID — $search first, then filter and shape the results
db.movies.aggregate([
  { $search: { text: { query: "space", path: "plot" } } },
  { $match:  { year: { $gte: 2000 } } },
  { $project: { title: 1, year: 1, score: { $meta: "searchScore" } } },
  { $limit: 10 }
])

The one narrow exception, added in MongoDB 6.0: $search may appear as the first stage inside a $lookup sub-pipeline.

Selecting the Index

If the index option is omitted, $search queries the index named default. When you created a named index, you must name it in the query — this mismatch is a classic exam distractor:

db.movies.aggregate([
  { $search: { index: "movieTitleIdx", text: { query: "space", path: "title" } } }
])

Common Search Operators

OperatorPurpose
textAnalyzed full-text match; supports fuzzy
phraseOrdered term sequence, with optional slop
autocompleteSearch-as-you-type against an autocomplete-typed field
equalsExact match on boolean, number, date, ObjectId, or string token
rangeNumeric, date, or string bounds (gt, gte, lt, lte)
wildcard / regexPattern matching against indexed terms
existsDocuments in which the indexed path is present
compoundCombines the above via must, mustNot, should, filter

Compound Queries and Scoring

compound is where relevance is actually shaped, and each clause behaves differently:

  • must — the clause is required and contributes to the score.
  • should — optional; every match raises the score. minimumShouldMatch sets how many should clauses must match.
  • mustNot — excludes matching documents.
  • filter — required, but does not affect the score. Use it for hard constraints such as a category or a date window so they cannot distort ranking.
db.movies.aggregate([
  { $search: {
      index: "movieTitleIdx",
      compound: {
        must:   [ { text: { query: "space", path: "title" } } ],
        should: [ { text: { query: "odyssey", path: "plot" } } ],
        filter: [ { range: { path: "year", gte: 1990 } } ]
      }
  } },
  { $project: { title: 1, score: { $meta: "searchScore" } } }
])

Relevance is exposed through the $meta: "searchScore" expression, mirroring the way $text exposes $meta: "textScore". Results already arrive sorted by descending score, so adding { $sort: { score: -1 } } is redundant unless you are combining scores with other criteria.

4. MongoDB Search vs. $text Full-Text Search

The guide's earlier coverage of text indexes (createIndex({ field: "text" }) queried with $text) is a different feature, and the exam will offer one as a distractor for the other.

$text + text indexMongoDB Search
Created bycreateIndex({ f: "text" })createSearchIndex(...)
Queried by$text inside find() or $match$search / $searchMeta
EngineMongoDB's own text indexApache Lucene via mongot
Text indexes per collectionOneMany search indexes allowed
Relevance metadata{ $meta: "textScore" }{ $meta: "searchScore" }
Fuzzy / autocomplete / highlightingNot supportedSupported
FacetingNot supported$searchMeta with facets
Stage positionAnywhere a filter is validMust be first

How to read these questions on exam day:

  1. If the stem says "define a search index", the answer uses createSearchIndex() with a mappings object — not createIndex(), and not a bare field list.
  2. If the stem shows a pipeline, check stage order first. $search anywhere but position one is wrong before you even read the operator.
  3. If the definition sets dynamic: false, confirm a fields object is present.
  4. If the query names an index, confirm that name was actually created; if no name was given at creation, the index is default.
  5. filter clauses inside compound are the correct tool when a constraint must not influence relevance ranking.
Test Your Knowledge

A developer runs db.movies.aggregate([{ $match: { year: { $gte: 2000 } } }, { $search: { text: { query: 'space', path: 'plot' } } }]) and the pipeline fails with an error. What is the defect?

A
B
C
D
Test Your Knowledge

A developer creates a search index with db.products.createSearchIndex({ mappings: { dynamic: true } }), omitting the name argument. A later $search stage that specifies index: 'products_idx' errors because the index does not exist. Why?

A
B
C
D
Test Your Knowledge

A search index is defined as { mappings: { dynamic: false } } with no other keys. The index builds without error, but every $search query against it returns zero documents. What is wrong?

A
B
C
D
Test Your Knowledge

Which statement correctly distinguishes a MongoDB Search index from a standard database index?

A
B
C
D