Firestore Indexes Diff
A CLI tool that reports which Firestore indexes and field overrides exist in one index configuration file but are missing from another, so you can catch gaps before deploying across environments.
Features
- Compare Firestore index configurations between environments
- Report indexes and field overrides that the target file lacks
- Normalises the implicit trailing
__name__field, so a server export compares correctly against a hand-written file - Colourful CLI output when attached to a terminal, quiet and parseable when piped
- Export differences to JSON files for further analysis
Requirements
- Node.js 22.12 or newer. Earlier versions cannot parse the JSON import
attribute this CLI uses.
npm installwill warn you if your Node is too old.
You also need two Firestore index configuration files. See the Cloud Firestore Index Definition Reference for the file format.
Quick Start
Using pnpm dlx (Recommended)
pnpm dlx firestore-indexes-diff --source dev.json --target prod.json
# npx firestore-indexes-diff --source dev.json --target prod.json
# yarn dlx firestore-indexes-diff --source dev.json --target prod.json
Global Installation
pnpm add -g firestore-indexes-diff
# npm install -g firestore-indexes-diff
# yarn global add firestore-indexes-diff
diff-indexes --source dev.json --target prod.json
Try it against the bundled samples
The repository ships two sample files. sample/dev_indexes.json declares a
users index and a posts index; sample/prod_indexes.json declares only the
users one.
git clone https://github.com/omar-dulaimi/firestore-indexes-diff.git
cd firestore-indexes-diff
pnpm install
pnpm run build
node lib/bin/index.js --source sample/dev_indexes.json --target sample/prod_indexes.json
cat diff-indexes.json
That writes a diff-indexes.json holding the one posts index that production
is missing. No diff-field-overrides.json appears, because neither sample file
declares any field overrides.
Options
| Option | Alias | Type | Required | Description |
|---|---|---|---|---|
--source |
-s |
string | Source indexes file path | |
--target |
-t |
string | Target indexes file path | |
--version |
-v |
Show version number | ||
--help |
-h |
Show help |
Paths may be relative to the current directory or absolute.
Output
The comparison is one-directional: it answers "what is in the source file but not in the target file". Results are written to the current directory.
| File | Contents |
|---|---|
diff-indexes.json |
Indexes present in the source, missing in target |
diff-field-overrides.json |
Field overrides present in source, missing in target |
Each file is written only when there is something to report, and an existing file is deleted when there is nothing to report. That keeps the presence of a file a truthful signal, so a CI check can test for it without being tripped up by output left over from an earlier run.
The exit code is 0 for a successful comparison, whether or not differences were
found, and 1 if a file could not be read, is not valid JSON, or is not a
Firestore index file.
How indexes are compared
Two indexes are considered the same when their collectionGroup, queryScope,
apiScope, density, multikey, unique and fields all agree, following the
equality rules that firebase-tools uses.
Some details worth knowing:
- The implicit
__name__field is normalised. Firestore appends a trailing__name__field to every composite index, so a file produced byfirebase firestore:indexesalways carries it while a hand-writtenfirestore.indexes.jsonnever does. Both sides are normalised before comparing, otherwise every index in a server export would be reported as missing. - Field order within an index matters, because it is significant to
Firestore.
[a, b]and[b, a]are different indexes. - Key order inside the JSON does not matter. A field written as
{"fieldPath": "a", "order": "ASCENDING"}matches one written as{"order": "ASCENDING", "fieldPath": "a"}. - An omitted optional value equals its default. An absent
apiScopematches an explicitANY_API, and an absentttlon a field override matches an explicitttl: false, which is what a server export writes. - Field override index lists are order-insensitive, matching firebase-tools: what matters is the set of enabled modes, not the order they appear in.
Workflow Examples
Development to Production Comparison
# Which indexes does production not have yet?
pnpm dlx firestore-indexes-diff --source dev-indexes.json --target prod-indexes.json
Staging Environment Sync
# Ensure staging has all the indexes from development
pnpm dlx firestore-indexes-diff --source dev-indexes.json --target staging-indexes.json
CI/CD Integration
firebase firestore:indexes needs the Firebase CLI and credentials for the
project, so this snippet assumes both are already configured.
- name: Check Firestore Index Completeness
run: |
# Export the indexes currently deployed to production.
firebase firestore:indexes --project "$PROJECT_ID" > prod-indexes.json
# Compare the committed index file against what is deployed.
npx firestore-indexes-diff --source firestore.indexes.json --target prod-indexes.json
# Fail if anything in the committed file is not deployed yet.
if [ -f "diff-indexes.json" ]; then
echo "Missing indexes detected!"
cat diff-indexes.json
exit 1
fi
Multi-Environment Validation
# Check multiple environments in sequence
for env in dev staging prod; do
echo "Checking $env environment..."
npx firestore-indexes-diff \
--source master-indexes.json \
--target "$env-indexes.json"
done
Development
# Clone the repository
git clone https://github.com/omar-dulaimi/firestore-indexes-diff.git
cd firestore-indexes-diff
# Install dependencies
pnpm install
# Compile TypeScript to lib/
pnpm run build
# Compile in watch mode
pnpm run dev
# Lint and run the test suite
pnpm run lint
pnpm run test:run
Verifying the published artifact
The test suite includes end-to-end tests that spawn the compiled binary, because unit tests that import the source modules directly cannot see a failure in the CLI entry point. On top of that, this script packs the tarball, installs it into an empty directory and runs the documented command against it:
bash ./scripts/verify-package.sh
CI runs it on Node 22 and 24 for every push.
License
MIT Omar Dulaimi