Firebase Firestore Cheat Sheet
Firestore's modular JS SDK for reading, writing, querying, and listening to real-time document data in a NoSQL collection model.
SDK Basics (v9 modular)
Initializing Firestore and writing documents.
import { initializeApp } from "firebase/app";import { getFirestore, collection, doc, setDoc, addDoc } from "firebase/firestore";const app = initializeApp(firebaseConfig);const db = getFirestore(app);// Add a document with an auto-generated IDawait addDoc(collection(db, "users"), { email: "[email protected]" });// Set a document with a known IDawait setDoc(doc(db, "users", "u1"), { email: "[email protected]" });
Queries & Reads
Filtering, ordering, and limiting results.
import { query, where, orderBy, limit, getDocs, collection } from "firebase/firestore";const q = query( collection(db, "users"), where("age", ">=", 18), orderBy("age"), limit(10));const snapshot = await getDocs(q);snapshot.forEach(docSnap => console.log(docSnap.id, docSnap.data()));
Real-Time Listeners & Updates
Subscribing to changes and modifying data.
import { onSnapshot, updateDoc, deleteDoc, doc } from "firebase/firestore";const unsubscribe = onSnapshot(doc(db, "users", "u1"), (snap) => { console.log(snap.data());});await updateDoc(doc(db, "users", "u1"), { email: "[email protected]" });await deleteDoc(doc(db, "users", "u1"));
Data Model & Rules
How Firestore organizes and secures data.
- Collection- a named group of documents, e.g. "users"
- Document- a JSON-like record with fields, max 1MB, stored in a collection
- Subcollection- a collection nested inside a document
- Security Rules- declarative rules controlling read/write access per document path
- Composite index- required for queries that filter or sort on multiple fields
- Offline persistence- the SDK caches data locally and syncs automatically on reconnect
Transactions & Batched Writes
Grouping multiple document changes atomically, either read-dependent or blind.
import { runTransaction, writeBatch, doc, increment } from "firebase/firestore";// Transaction: reads then writes atomically, auto-retries on contentionawait runTransaction(db, async (tx) => { const ref = doc(db, "accounts", "a1"); const snap = await tx.get(ref); const balance = snap.data().balance; if (balance < 50) throw new Error("insufficient funds"); tx.update(ref, { balance: balance - 50 }); tx.update(doc(db, "accounts", "a2"), { balance: increment(50) });});// Batched write: up to 500 writes, atomic, no reads, no auto-retryconst batch = writeBatch(db);batch.set(doc(db, "users", "u1"), { email: "[email protected]" });batch.update(doc(db, "users", "u2"), { plan: "pro" });batch.delete(doc(db, "users", "u3"));await batch.commit();
Advanced Security Rules
Using helper functions and request context to enforce row-level access control.
rules_version = '2';service cloud.firestore { match /databases/{database}/documents { function isSignedIn() { return request.auth != null; } function isOwner(userId) { return isSignedIn() && request.auth.uid == userId; } match /users/{userId} { allow read: if isSignedIn(); allow write: if isOwner(userId); match /orders/{orderId} { // Cross-document read to check a role stored on the parent user doc allow read: if isOwner(userId) || get(/databases/$(database)/documents/users/$(userId)).data.role == 'admin'; allow create: if isOwner(userId) && request.resource.data.total is number && request.resource.data.total > 0; } } }}
Aggregation & Collection Group Queries
Server-side counts/sums and querying same-named subcollections across the whole database.
import { getCountFromServer, getAggregateFromServer, sum, average, collectionGroup, query, where } from "firebase/firestore";// Count without reading/billing for every documentconst countSnap = await getCountFromServer(collection(db, "orders"));console.log(countSnap.data().count);// Sum/average in one server-side aggregation callconst aggSnap = await getAggregateFromServer(collection(db, "orders"), { totalRevenue: sum("total"), avgTotal: average("total")});// collectionGroup matches every subcollection named "reviews", regardless of parentconst reviewsQ = query(collectionGroup(db, "reviews"), where("rating", ">=", 4));
Cursor-Based Pagination
Fetching results page-by-page using document snapshots as cursors instead of offsets.
import { query, collection, orderBy, limit, startAfter, getDocs } from "firebase/firestore";async function fetchPage(cursor) { const base = [collection(db, "users"), orderBy("createdAt"), limit(20)]; const q = cursor ? query(...base, startAfter(cursor)) : query(...base); const snap = await getDocs(q); const lastDoc = snap.docs[snap.docs.length - 1]; return { docs: snap.docs, nextCursor: lastDoc };}// offset() exists but re-reads (and bills for) every skipped document — avoid for deep pagination
Advanced Concepts & Limits
Details that matter once an app scales past a prototype.
- Transaction contention- concurrent transactions on the same document retry with backoff; document write rate is capped at ~1/sec sustained
- Composite index exemptions- single-field indexes are automatic; range/inequality filters on multiple fields need a manually created composite index
- TTL policies- a configured timestamp field triggers automatic background deletion, similar to DynamoDB TTL
- Firestore bundles- precomputed, cacheable snapshots of query results served from a CDN to cut initial read costs
- Emulator Suite- local Firestore/Auth/Functions emulators for offline development and rules unit testing
- Converters (withConverter)- typed read/write mappers between Firestore documents and application classes
- Document size limit- 1MiB per document, including field names — deeply nested or array-heavy documents can hit this fast
Firestore bills per document read/write/delete, not per query complexity — model your data so common queries touch as few documents as possible rather than optimizing the query syntax itself.