Firestore
firestoreDatabase33 lines
Firebase's document database, queried directly from the client under security rules.
Current — as published in v1.3.2. Written by hand for Firestore, not generated.
The rule
Queries
- `limit()` on every query. An unbounded read on a growing collection costs money proportional to the collection.
- Paginate with cursors (
startAfter), not offsets. - Composite indexes belong in
firestore.indexes.json, committed. An index created by clicking a console link does not exist in the next environment. - No joins. Denormalise the fields a screen reads, and keep the write path that updates every copy.
Listeners
- Every
onSnapshotreturns an unsubscribe. Return it from the effect. - A leaked listener bills for as long as the tab is open.
Writes
setDocreplaces;updateDocmerges. Pick deliberately.serverTimestamp()for anything you order by — client clocks are unreliable.- Batches for related writes; transactions when you read then write atomically.
Modelling
- Shallow and wide beats deep and nested: a document is always read in full.
- Model around the screens that read the data, not around entities.
Never
- Never read a collection without a bound.
- Never rely on client-side filtering for access control — rules enforce it.
- Never store personal data in a document whose rules allow broad reads.
As each tool receives it
The same guidance, in the shape each editor reads. Cursor and Claude Code are written separately by hand; the rest are derived from the Cursor rule.
.cursor/rules/firestore.mdc
---
description: Firestore conventions
globs: ["src/services/firebase/**", "firestore.rules", "firestore.indexes.json"]
alwaysApply: false
---
# Firestore
## Queries
- **`limit()` on every query.** An unbounded read on a growing collection costs
money proportional to the collection.
- Paginate with cursors (`startAfter`), not offsets.
- Composite indexes belong in `firestore.indexes.json`, committed. An index
created by clicking a console link does not exist in the next environment.
- No joins. Denormalise the fields a screen reads, and keep the write path that
updates every copy.
## Listeners
- Every `onSnapshot` returns an unsubscribe. Return it from the effect.
- A leaked listener bills for as long as the tab is open.
## Writes
- `setDoc` replaces; `updateDoc` merges. Pick deliberately.
- `serverTimestamp()` for anything you order by — client clocks are unreliable.
- Batches for related writes; transactions when you read then write atomically.
## Modelling
- Shallow and wide beats deep and nested: a document is always read in full.
- Model around the screens that read the data, not around entities.
## Never
- Never read a collection without a bound.
- Never rely on client-side filtering for access control — rules enforce it.
- Never store personal data in a document whose rules allow broad reads.
What else this module writes
Selecting Firestore contributes more than a rule file — all of it merged with every other module you pick, with conflicts resolved rather than duplicated.
Folders
src/services/firebase/collections/
Related
Put this in your repo
Adds Firestore to a project this tool generated, without starting over. Files you have hand-edited are left alone.