Wildcard indexes for unpredictable fields
Index every field under a path (or the whole document) when the queried fields are not known in advance, and understand the trade-offs versus the attribute pattern.
- Create single and compound wildcard indexes
- Read wildcard index plans ($_path)
- Know what wildcard indexes cannot do
- Choose between wildcard indexes and the attribute pattern
conceptThe concept
A wildcard index (4.2+) indexes every field under a path: { "attrs.$**": 1 } covers attrs.color, attrs.size, attrs.voltage and any field added later. { "$**": 1 } with a wildcardProjection includes or excludes chosen paths across the whole document.
Each index entry stores the field path and the value, so a query on attrs.color scans only the keys for that path. Since 7.0, compound wildcard indexes such as { tenantId: 1, "attrs.$**": 1 } combine a normal prefix with wildcard fields.
Limitations: no unique or TTL wildcard indexes; they cannot be shard keys; they do not support $exists: false or exact matches on whole arrays or embedded documents; and a single wildcard index serves one wildcard field predicate per query plan.
whyWhy it matters
Product catalogs, form builders and multi-tenant SaaS settings let users define fields. Creating an index per possible field is impossible, and unindexed queries become collection scans as the data grows.
Use caseReal-world scenario
A marketplace stores category-specific specs in attrs: electronics have voltage, apparel has size and fabric. Search filters on any one of them. A { "attrs.$**": 1 } index serves all filters; the team still adds a dedicated compound index for the single hottest query, which also needs a sort.
Try itExamples
Run and edit these in the interactive lesson.
db.lab_items.drop()
db.lab_items.insertMany([
{ sku: "TV-1", category: "electronics", attrs: { screen: 55, voltage: 220, color: "black" } },
{ sku: "TV-2", category: "electronics", attrs: { screen: 65, voltage: 110, color: "silver" } },
{ sku: "TS-1", category: "apparel", attrs: { size: "M", fabric: "cotton", color: "black" } },
{ sku: "TS-2", category: "apparel", attrs: { size: "L", fabric: "linen", color: "white" } }
])
db.lab_items.createIndex({ "attrs.$**": 1 })
db.lab_items.find({ "attrs.color": "black" }, { _id: 0, sku: 1 })db.lab_items.find({ "attrs.fabric": "cotton" }).explain("executionStats").queryPlanner.winningPlandb.lab_items.createIndex({ "$**": 1 }, { wildcardProjection: { attrs: 1, category: 1 }, name: "wc_attrs_category" })
db.lab_items.getIndexes().map(i => ({ name: i.name, key: i.key, proj: i.wildcardProjection }))internalsUnder the hood
Keys are stored as ($_path, value) pairs, which is why explain shows keyPattern: { $_path: 1, "attrs.color": 1 }. Indexing everything inflates index size and write cost, so restrict the path. The attribute pattern (attrs: [{ k, v }] with { "attrs.k": 1, "attrs.v": 1 }) is the older alternative: one compound multikey index, works everywhere, and can be combined with other fields in a normal compound index.
Avoid / preferDo this, not that
db.products.createIndex({ "$**": 1 })db.products.createIndex({ "attributes.$**": 1 })Indexing every field of every document makes the index large and slows all writes. Scope the wildcard to the sub-document whose keys actually vary.
// a wildcard index to replace known, stable query shapes
db.orders.createIndex({ "$**": 1 })db.orders.createIndex({ status: 1, createdAt: -1 })A wildcard index indexes each field separately and cannot provide multi-field sorts like a compound index. For queries you know, purpose-built indexes are smaller and faster.
PitfallsCommon mistakes
- Indexing the whole document with { "$**": 1 } and paying for every field on every write.
- Expecting a wildcard index to enforce uniqueness or expire documents.
- Using a wildcard index where one compound index on known fields would serve the query and its sort better.
LimitsWhen not to use it
Use wildcard indexes for genuinely unpredictable keys (user-defined attributes, metadata bags). Do not use them as a way to skip index design: they cannot support compound sort orders across fields, and they grow with every new key users invent.
SecuritySecurity notes
A wildcard index on user-controlled keys lets clients grow your index with arbitrary field names. Validate attribute names and counts at write time (JSON Schema or application checks) so one tenant cannot bloat storage and slow writes for everyone.
ChallengeTry it yourself
Create a wildcard index on products.attributes, then show that a query on attributes.brand uses it: report the index name in the plan and the number of matching products.
Show one solution
db.products.createIndex({ "attributes.$**": 1 })
const brand = db.products.findOne().attributes.brand
const e = db.products.find({ "attributes.brand": brand }).explain("executionStats")
printjson({ brand, index: JSON.stringify(e.queryPlanner.winningPlan).match(/"indexName":"([^"]+)"/)[1], matches: e.executionStats.nReturned })InterviewQuestions you should be able to answer
When would you use a wildcard index instead of the attribute pattern?
When documents use natural field names that vary and you want to keep that shape, and queries usually filter on one dynamic field. The attribute pattern ([{ k, v }] with a compound index on k, v) works on every version, combines cleanly with other fields and supports multiple attribute predicates, but changes the document shape. Since 7.0 compound wildcard indexes close part of the gap by adding a fixed prefix such as tenantId.
QuizCheck your understanding
4 questions with instant feedback in the interactive lesson; a wrong answer goes into your review deck.