What it means
$text searches words in string fields, with stemming (“index” finds “indexing”) and a relevance
score. It doesn’t scan documents: it reads a text index. When the collection has no text index,
MongoDB refuses the query with code 27, IndexNotFound, and this message. The same happens inside
an aggregation’s $match.
A collection can have only one text index, but it can cover several fields, or every string field with a wildcard.
Common causes
- The index was never created on this server: it exists in development but not in production,
or the code that creates it (Mongoose’s
autoIndex, a migration) didn’t run. - The wrong collection or database, for example a misspelt name or a collection created by a test. A collection that doesn’t exist has no indexes.
- The index was dropped, perhaps to replace it, and the new one wasn’t created.
- Expecting Atlas Search.
$textuses the database’s own text index; Atlas Search is a separate feature queried with the$searchaggregation stage and its own search index.
How to fix it
Check the indexes
db.articles.getIndexes()
A text index shows key: { _fts: "text", _ftsx: 1 } and its fields in weights.
Create a text index
List every field the search should cover in one index:
db.articles.createIndex({ title: "text", body: "text" })
Or cover every string field: db.articles.createIndex({ "$**": "text" }). Weights make matches in
some fields count more: { weights: { title: 5, body: 1 } } as the second argument. Building the
index reads the whole collection, so on a large one, create it at a quiet time.
Search, sorted by relevance
db.articles.find(
{ $text: { $search: "index" } },
{ title: 1, score: { $meta: "textScore" } }
).sort({ score: { $meta: "textScore" } })
Replace the text index to change its fields
A second text index is refused (see index already exists). Drop the old one by name, then create the new one:
db.articles.dropIndex("title_text_body_text")
db.articles.createIndex({ title: "text", body: "text", summary: "text" })
Or use $regex for simple matches
For a substring or prefix match on one field, $regex needs no text index:
db.articles.find({ title: { $regex: "index", $options: "i" } })
It doesn’t stem words or rank results, and only an anchored, case-sensitive pattern (/^Index/)
uses an ordinary index on the field efficiently.
Reproduce it
MongoDB 8.0.32, mongosh 2.12.0, two articles and no text index:
db.articles.find({ $text: { $search: "index" } })
MongoServerError[IndexNotFound]: text index required for $text query
The error’s code was 27. An aggregation with $match: { $text: … } gave the same message. After
createIndex({ title: "text", body: "text" }) (named title_text_body_text), the search found
“Indexing in MongoDB” with a score of 1.35. A second text index on summary was refused:
MongoServerError[IndexOptionsConflict]: An equivalent index already exists with a different name and options. Requested index: { v: 2, key: { _fts: "text", _ftsx: 1 }, name: "summary_text", … }, existing index: { v: 2, key: { _fts: "text", _ftsx: 1 }, name: "title_text_body_text", … }
The $regex query with $options: "i" found the same article without a text index.
In Inlet
Queries use mongosh syntax, so db.articles.getIndexes() shows what the collection has, and
createIndex and the $text search run as they do in mongosh.