You have a MongoDB collection full of documents that contain both text to search and GPS coordinates to filter by distance. If you created a separate text index and a separate 2dsphere index, queries combining $text and $near run slow or return performance errors. The issue is that MongoDB cannot use two different indexes in the same query: it must pick one and filter the other side in memory. The solution? A compound index that merges text and geospatial into a single structure.
We, at Meteora Web, have seen dozens of MongoDB projects where separate indexes forced the database to scan thousands of documents to satisfy a simple "open places near me that mention pizza" search. With a compound text-geospatial index, the same query runs in milliseconds. This guide explains how to create them, when to use them, and what limitations to keep in mind.
Why does a MongoDB compound text geospatial index speed up hybrid searches?
When MongoDB executes a query combining $text and a geospatial operator (e.g., $near, $geoWithin), the query engine must decide which index to use first. By default it picks the index it estimates to be more selective, but the other field is filtered afterwards without an index. Result: a full scan of a potentially huge second set of documents.
A compound text geospatial index solves this because MongoDB can satisfy both filters by exploring a single data structure. The index combines a text index field (with keywords, weights, and language) and a 2dsphere field (coordinates). The query performs only one index scan, drastically reducing the number of documents to examine.
Sponsored Protocol
Practical example: suppose a collection places with fields name (text-indexed string) and location (GeoJSON Point). Without a compound index, a query like "find places within 1 km of a point that contain the word 'pizza'" must either use the text index to find all documents with 'pizza' (maybe hundreds) and then geo-filter, or use the geospatial index to find nearby places (thousands) and then text-filter. With the compound index, MongoDB applies both filters simultaneously in the index.
How to create a compound text and geospatial index on an existing collection?
The syntax is straightforward, but there are strict rules. In a MongoDB compound index, the text index type must be specified as "text" and the geospatial field as "2dsphere". You cannot arbitrarily reverse the order: the text index must be the first field in the index definition if you want $text to be used first (which is almost always desirable).
Here is the command to create a compound index on a places collection:
Sponsored Protocol
db.places.createIndex(
{ name: "text", location: "2dsphere" },
{
name: "idx_text_geo",
default_language: "english",
weights: { name: 10 }
}
)Explanation:
- name: "text" — declares that the
namefield is indexed as a text index (you can also add multiple text fields, e.g.,nameanddescription, separated by commas, but one of them must be first). - location: "2dsphere" — indicates that
locationis a GeoJSON field (or legacy coordinate pair). - default_language: "english" — for stemming and stop words (crucial for English queries).
- weights — assigns a higher weight to the
namefield for text ranking.
Note: MongoDB does allow a compound index with multiple text fields (e.g., name: "text", description: "text", location: "2dsphere"). The first field must be a text index, and subsequent text fields are concatenated with commas. Example:
db.places.createIndex(
{ name: "text", description: "text", location: "2dsphere" },
{
name: "idx_name_desc_geo",
default_language: "english",
weights: { name: 10, description: 5 }
}
)Important restriction: you cannot insert a non-text field between two text fields. The compound text index must start with one or more text fields, followed by other types (geospatial, regular, etc.).
What are the practical limitations of a MongoDB compound text geospatial index?
Not everything is perfect. There are limitations you must know to avoid surprises:
Sponsored Protocol
- You cannot use
$textwith$nearon a compound index if the field order is wrong. The index must have the text field first in the definition order for$textto be used in the query. Also, the query must necessarily include a$textcondition for the index to be used. - The 2dsphere index inside a compound does not support
$geoNearas a separate aggregation stage.$geoNearrequires a standalone geospatial index; it cannot be executed on a compound index. For aggregation pipelines with$geoNear, you need a separate 2dsphere index. - The text index cannot be first if you want to run a purely geospatial query without
$text. In that case the compound index won't be used; better to have a separate 2dsphere index to cover that scenario as well. - Index size: a text index consumes space, and combined with geospatial data it can become very large. Monitor RAM usage.
We, at Meteora Web, always recommend keeping a standalone 2dsphere index for purely geospatial queries and a compound text-geospatial index for hybrid queries. No single index can do everything.
How to verify the performance of a text-geospatial index with explain()?
Don't trust intuition. Use .explain("executionStats") to see if MongoDB is using the compound index and how many documents it examines. Example query:
Sponsored Protocol
db.places.explain("executionStats").find(
{
$text: { $search: "pizza" },
location: {
$near: {
$geometry: { type: "Point", coordinates: [14.268, 40.8518] },
$maxDistance: 1000
}
}
}
)In the explain() output, check:
- winningPlan.inputStage.stage — should be
IXSCANon the indexidx_text_geo. - executionStats.totalDocsExamined — should be low, ideally equal to the number of returned documents (
nReturned). - executionStats.executionTimeMillis — under 10ms for small/medium collections.
If you see COLLSCAN or FETCH with many examined documents, the index is not being used correctly. Possible causes: wrong field order, missing $text in the query, or index not present.
What to do when the compound text geospatial index is not enough?
Sometimes, even with the right index, performance is not acceptable for very high volumes (millions of documents with many text terms). Consider:
- Sharding: partition the collection across multiple nodes using a shard key that balances load. Note: compound text-geospatial indexes cannot be used as shard key directly.
- Pre-filtering with
$geoWithininstead of$nearif you don't need distance ordering.$geoWithinis more efficient when combined with$text. - Change your data model: if the text to search is limited (e.g., fixed categories), consider using a regular field (with a normal index) instead of a text index, and combine it with the geospatial index in a regular compound index (non-text).
- Use Atlas Search: if you are on MongoDB Atlas, its full-text search service based on Lucene is often more performant than native text indexes for hybrid queries.
In any case, the golden rule: always measure with explain() before and after optimization.
Sponsored Protocol
What to do next
- Check your collections: run
db.collection.getIndexes()and look for compound indexes starting withtextand containing2dsphere. - Identify slow queries: enable slow query logging (slowms) and look for queries with
$textcombined with$nearor$geoWithin. - Create the compound index following the syntax above, ensuring the default language is correct.
- Test with explain() to confirm the index is used and numbers are satisfactory.
- Do not delete the standalone geospatial index: you need it for geo-only queries or
$geoNearin aggregations.
We, at Meteora Web, deal with these optimizations daily. If you have a real dataset and want advice, check out our pillar guide on MongoDB covering the full database lifecycle. For specific questions, contact us.