You write a perfectly innocent-looking Firestore query:
const q = query(
collection(db, 'posts'),
where('authorId', '==', uid),
where('status', '==', 'published'),
orderBy('createdAt', 'desc')
);
Works fine in the local emulator. Deploys to prod and explodes:
FAILED_PRECONDITION: The query requires an index. You can create
it here: https://console.firebase.google.com/...
Firestore’s strict rule: any query with multi-field where, or where + orderBy on different fields, needs a precomputed composite index. There’s no “auto-use index” like PostgreSQL — indexes are explicit schema you have to declare.
Common causes
Ordered by hit rate, highest first.
1. where + orderBy on different fields
// Needs composite index: (authorId, createdAt)
query(coll, where('authorId', '==', x), orderBy('createdAt', 'desc'))
Any where field + orderBy on a different field = composite index required.
How to spot it: where field ≠ orderBy field?
2. Multiple inequality operators (!=, <, <=, >, >=, NOT_IN)
// Index required
query(coll, where('status', '!=', 'archived'), orderBy('createdAt'))
Inequality filters alone require an index. Multiple inequality filters definitely do.
How to spot it: where uses !=, <, >, not-in, array-contains-any?
3. Multiple equality wheres + any orderBy
// Needs composite index: (status, authorId, createdAt)
query(coll,
where('status', '==', 'published'),
where('authorId', '==', x),
orderBy('createdAt', 'desc')
)
Two or more wheres (even all equality) + orderBy = composite index.
How to spot it: ≥ 2 where clauses?
4. First production run of this query
Emulator auto-creates virtual indexes on demand; production requires manual creation. Code merge → deploy → first user request → boom.
How to spot it: Emulator works, prod errors.
5. Index exists but still building
New indexes take minutes to hours (depending on data volume). During build, queries still throw FAILED_PRECONDITION.
How to spot it: Firebase Console → Firestore → Indexes → status is “Building” not “Enabled.”
6. Index built with wrong direction (asc vs desc)
query(coll, where('authorId', '==', x), orderBy('createdAt', 'desc'))
Must be indexed as (authorId asc, createdAt desc). If you built desc + asc, it doesn’t match.
How to spot it: Open the error link — compare its suggested schema with existing indexes.
Shortest path to fix
Step 1: Click the URL in the error to auto-create
FAILED_PRECONDITION: The query requires an index.
You can create it here:
https://console.firebase.google.com/project/your-app/database/firestore/indexes?create_composite=...
The URL prefills the schema. Sign in, click Create Index. Build takes minutes to hours (depends on data size).
Step 2: Sync to firestore.indexes.json
Creating only via Console can be lost (a later deploy can overwrite). Export to code:
firebase firestore:indexes > firestore.indexes.json
Or maintain manually:
{
"indexes": [
{
"collectionGroup": "posts",
"queryScope": "COLLECTION",
"fields": [
{ "fieldPath": "authorId", "order": "ASCENDING" },
{ "fieldPath": "createdAt", "order": "DESCENDING" }
]
}
]
}
Deploy:
firebase deploy --only firestore:indexes
Step 3: Wait for build to finish
firebase firestore:indexes --project my-prod
# Or check Console for status: Building / Enabled
Multi-million-doc collections can take hours. Until done, errors persist.
Step 4: Simplify the query if possible
// Complex (needs composite index)
query(coll,
where('status', '==', 'published'),
where('authorId', '==', x),
orderBy('createdAt', 'desc')
)
// Simplified (single field, no composite index)
query(coll,
where('authorId_status', '==', `${x}_published`), // composite key on write
orderBy('createdAt', 'desc')
)
This “composite key on write” trick saves an index but requires you maintain authorId_status at write time.
Step 5: Catch it early in emulator
# Local emulator
firebase emulators:start --only firestore
# Run your app + e2e tests
# Whenever the emulator emits FAILED_PRECONDITION, add the suggested index
# Pre-deploy dry-run shows index diffs
firebase firestore:indexes
Step 6: Direction alignment
query(..., orderBy('a', 'desc'), orderBy('b', 'asc'))
Index must be (a DESCENDING, b ASCENDING). Auto-generated console links align; hand-written firestore.indexes.json is risky.
Prevention
- Run every new query on the emulator first; add the required index to
firestore.indexes.jsonproactively - Commit
firestore.indexes.jsonto git; PR review surfaces index changes - CI step:
firebase firestore:indexes --dry-rundiffs prod vs. repo - Add indexes to large collections (millions of docs) during off-hours — building can affect reads
- For stable query patterns, maintain a “query pattern → index” reference doc for new contributors
- Don’t create indexes for every ad-hoc query — audit and remove unused ones periodically (each index has storage cost)
- For complex queries, consider denormalizing (redundant fields) to reduce multi-where needs
Related
Tags: #Firebase #Debug #Troubleshooting