Firestore Query & Index Design
A query plan is an index proof: name what the product must read, prove each query is allowed by rules, and extract the composite indexes the code requires. Model reads first — slow Firestore apps usually download too many documents, not scan too many.
Steps
Inventory query intents. For each screen, report, listener, or search: collection path (or collection group), filters,
orderByfields, limit, cursor strategy, auth identity, and expected result size. Complete when every read the product performs is a row in the inventory.Choose read shapes. Options per intent: direct document read, top-level collection query, subcollection query (removes a
wherefor single-parent reads), collection-group query (cross-parent subcollection reads), or a denormalized summary document. Complete when each intent has a shape and a one-line justification.Prove rules compatibility. Rules are not filters: a list query must be constrained (
whereon owner/membership fields) so it can only return documents its identity may read. Verify withfirestore_simulate_rulesper identity, or a hosted Rules Test API case (pyric verify --engine rules-test-api|both) per query. Complete when every list query has a matching rule + constraint pair.Write the query code in modular SDK shape inside a function body —
query(collection(db, ...), where(...), orderBy(...))— so the extractor can see it. Complete when each inventory row has code.Extract indexes. Run
pyric firestore indexes generateover the query code after every change. It writesfirestore.indexes.json-shaped config plus warnings. Zero extracted shapes means the source didn't expose a pattern (missing file, admin-chain syntax, no composite query) — report that and fix the source; never hand-write index JSON the extractor didn't produce. ReviewovershootSuspectedwarnings; a targeted@firestore-mutexannotation trims shapes enumerated from mutually exclusive branches. Complete when extraction succeeds with reviewed warnings.Deploy and confirm. Write the reviewed config to
firestore.indexes.json, then apply it withnpx firebase-tools deploy --only firestore:indexes. Confirm each build is ready in Firebase before exercising the query. Complete when every composite query has a ready index.Verify against data. Run representative queries with
firestore_query_where(seed viafirestore_batch_writeif needed) and confirm result shape and size match the inventory. Complete when each intent returns what its screen expects.
Reference — query rules
- Single-field queries index automatically; combined filters/order need composite indexes.
- Arrays take
array-contains/array-contains-any; use a real array, not a map-as-tag shape, when membership is the access pattern. - Paginate with cursors, never growing limits or offsets.
- Document reads are shallow — subcollections do not ride along.
- Denormalization is a spectrum: duplicate slow-changing display data when it removes repeated lookups or impossible joins; plan fan-out writes to keep copies consistent.