FAILED_PRECONDITION

The query requires an index. Here's the fix.

Fix the error you just hit, understand exactly which Firestore queries need a composite index and why, then stop rediscovering them one production failure at a time.

The error you just saw

Firestore throws this at query time, not at write time. The code is FAILED_PRECONDITION, and the message carries a URL that already contains the index definition Firestore worked out from your query.

text
FirebaseError: The query requires an index. You can create it here:
https://console.firebase.google.com/v1/r/project/<project-id>/firestore/indexes?create_composite=...

Opening that link and confirming is the fastest possible fix, and it is the right first move when production is broken. It has one limitation worth knowing before you rely on it: the index is created only in the project whose query failed. Your staging project, your teammates, and your next deploy know nothing about it.

The reproducible fix: firestore.indexes.json

The same index expressed as configuration. This file lives in your repository next to firebase.json, and deploying it applies the index to whichever project you are targeting.

json
{
  "indexes": [
    {
      "collectionGroup": "posts",
      "queryScope": "COLLECTION",
      "fields": [
        { "fieldPath": "tags", "arrayConfig": "CONTAINS" },
        { "fieldPath": "createdAt", "order": "DESCENDING" }
      ]
    }
  ],
  "fieldOverrides": []
}
bash
firebase deploy --only firestore:indexes

Two details that cause most of the confusion here. Array fields use arrayConfig: "CONTAINS" rather than order, because there is nothing to sort inside the array. And field sequence is not cosmetic: it must match the shape of the query. Equality filters come first, then the array-contains field, then the range or inequality field, then the remaining ordering fields. An index listing createdAt before tags will not satisfy the query above, and Firestore will keep asking for a new one.

Which queries need a composite index

Firestore indexes every field of every document automatically, in both directions, plus an array-contains index for array fields. That is why single-field queries never need setup. Composite indexes exist for the case a single index cannot answer: returning a filtered set already in the order you asked for.

array-contains + orderBy

where('tags', 'array-contains', 'firebase')
  .orderBy('createdAt', 'desc')

Firestore auto-creates an array-contains index for the tags field alone. The moment you sort the matches by a different field, it needs a composite index whose first entry is the array field and whose second entry is the sort field.

orderBy on multiple fields

orderBy('priority', 'desc')
  .orderBy('createdAt', 'desc')

Two sort fields is already a composite index. Single-field indexes cannot be merged to produce a stable multi-field ordering, so Firestore asks for an index covering both in that exact sequence.

Range filter + a different orderBy

where('score', '>', 100)
  .orderBy('createdAt', 'desc')

A range or inequality filter forces its field to be the first ordering key. Sorting by anything else afterwards needs a composite index listing score before createdAt.

Equality filter + orderBy

where('status', '==', 'published')
  .orderBy('publishedAt', 'desc')

The single most common trigger. Equality-filtered fields come first in the index, followed by the ordering field. This is why status/publishedAt and publishedAt/status are not interchangeable.

The underlying rule is the same in all four cases. Firestore answers a query by scanning one contiguous range of one index. If no single index can produce your filter and your sort order in one pass, it refuses rather than sorting in memory — which is also why query latency stays proportional to the size of the result set instead of the size of the collection.

For a longer walkthrough of index field order and the reverse-scan behaviour, see Firestore composite indexes explained.

What happens after you create one

The index does not exist the instant you click create. Firestore backfills an entry for every document already in the collection, and the query keeps failing with the same error until that finishes. The console shows the index as Building, then Enabled. On a small collection that is seconds. On a collection with millions of documents it can run for hours, which is worth knowing before you plan a release around it.

Indexes are not free after that either. Every index entry consumes storage, and every write to a document updates every index that covers it. A collection carrying a dozen composite indexes pays that cost on each write. Firestore also caps a database at 200 composite indexes, so accumulating one per accidental query eventually becomes a real constraint rather than a tidiness concern.

There is one more trap that makes indexes feel unpredictable: the Firestore emulator does not enforce them. Local development and emulator-backed tests will happily answer a query that production rejects. Nothing in your test suite catches a missing index, so the discovery almost always happens after deploy.

Keeping indexes in version control

Declare indexes on the model, generate the JSON

Console-created indexes drift because nothing links them to the query that needed them. Firemap's approach is to put the declaration next to the schema: stack an @Index decorator per composite index on the collection class, then generate firestore.indexes.json from it.

typescript
@Collection('posts')
@Index(['status', 'publishedAt'], { order: 'DESCENDING' })
@Index(['authorId', 'createdAt'])
class Post extends BaseModel {
  @Field({ type: 'string', required: true })
  status!: string;

  @Field({ type: 'timestamp', required: true })
  publishedAt!: Date;
}

Field order inside @Index is the index field order, and the optional order applies to the last field — the one you are sorting by. Running the generator writes the JSON file, which you deploy with the Firebase CLI exactly as before.

bash
firemap generate:indexes

firebase deploy --only firestore:indexes

Where the decorator stops

Being precise about this, because a generator that silently emits the wrong index is worse than no generator. @Index declares ordered composite indexes at collection scope. It does not emit arrayConfig: "CONTAINS" entries and it does not emit collection-group indexes, so the array-contains case at the top of this page is still authored directly in firestore.indexes.json or created from the console link. The two coexist in the same file.

The payoff is reviewability. A new index shows up in the same pull request as the query that introduced it, your teammate gets it on the next pull, and staging and production stop disagreeing. Full detail lives in the indexes documentation and the decorator reference. The same schema also drives security rules generation.

Frequently asked questions

Why does firestore array-contains with orderBy require a composite index?

Firestore maintains an automatic single-field index for array-contains on the array field, and another for the sort field, but it cannot intersect two indexes and still return rows in sorted order. A query like where('tags', 'array-contains', 'firebase').orderBy('createdAt', 'desc') needs one index that stores the createdAt ordering inside each tag bucket. That index has two entries: tags with arrayConfig CONTAINS, then createdAt DESCENDING, in that order.

Why does ordering by multiple fields require a composite index in Firestore?

Every orderBy after the first adds a tiebreaker that must be materialised in advance. Firestore serves ordered reads by scanning an index range in sequence, so the ordering has to already exist on disk. Two single-field indexes cannot be combined to produce a correct secondary sort, so any query with two or more orderBy clauses needs a composite index listing the fields in the same sequence and with the same directions the query uses.

How do I fix the query requires an index error quickly?

The error message contains a console URL with the exact index definition pre-filled. Open it, confirm, and Firestore starts building. That is the fastest fix, but it only creates the index in the project the failing query ran against. To make the fix reproducible, add the same definition to firestore.indexes.json and run firebase deploy --only firestore:indexes so staging and production both get it.

How long does a Firestore composite index take to build?

It depends on how many documents already exist in the collection. An empty or small collection is usually ready in under a minute. Collections with millions of documents can take hours because Firestore backfills an index entry for every existing document. The index shows as Building in the console during backfill, and queries that depend on it keep returning FAILED_PRECONDITION until it flips to Enabled.

Why do my queries work in the Firestore emulator but fail in production?

The Firestore emulator does not enforce composite index requirements. It answers any query you throw at it, so a missing index is invisible in local development and in most emulator-backed test suites. The first failure happens after deploy. Declaring indexes in firestore.indexes.json and reviewing them alongside the query that needs them is the practical defence, because it turns a runtime discovery into a code review item.

Do ascending and descending indexes count separately?

Direction is part of the index definition, but a single composite index also serves the exact reverse of every field at once. An index on status ASCENDING, publishedAt DESCENDING answers both that ordering and its full inverse. What it cannot answer is a partial flip, such as status ASCENDING with publishedAt ASCENDING. That combination needs its own index.

Put your indexes under version control

Design the schema, declare the indexes next to the collections that need them, and generate the config. Import an existing Firestore project and Firemap crawls it to build the starting schema for you.