Firebase Skill
When to use
- Architecting Firestore collection/subcollection structure and indexes
- Writing and testing Firestore Security Rules
- Implementing Firebase Auth (email/password, OAuth, custom tokens, custom claims)
- Writing Cloud Functions for Firebase (v1 HTTP, callable, background triggers)
- Configuring Firebase Hosting with rewrite rules to Cloud Functions or Cloud Run
- Setting up the Firebase Emulator Suite for local development and CI
- Diagnosing quota/billing issues, missing indexes, or security-rule denial errors
Workflow
- Design the data model before writing code — Firestore is schemaless but reads are charged per document. Denormalize data intentionally: embed sub-objects that are always read together; use subcollections for unbounded lists.
- Initialize the project:
firebase init→ select Firestore, Functions, Hosting, Emulators. Commit.firebasercandfirebase.json; gitignorefunctions/node_modules. - Write Security Rules early — create
firestore.ruleswith deny-all as the default, then open exactly what each user role needs. Mirror the same pattern forstorage.rules. - Test rules with the Emulator — run
firebase emulators:startand point the SDK atlocalhost:8080(Firestore) /localhost:9099(Auth). Use@firebase/rules-unit-testingfor automated rule tests. - Add composite indexes via
firestore.indexes.json— never create them only in the console; indexes defined in the file are reproducible across environments. - Cloud Functions: keep each function focused on a single trigger. Use
onCallfor client-invoked logic (handles Auth context automatically). UseonDocumentWritten/onDocumentCreatedfor server-side side effects. Avoid chaining background triggers (trigger → function → write → trigger) as this creates infinite loops. - Deploy incrementally:
firebase deploy --only firestore:rules, then--only functions, then--only hostingto limit blast radius. - Before production: run
firebase firestore:indexesto confirm indexes are deployed; check the Firebase console → Usage tab for quota headroom.
Standards
Do
- Use
FieldValue.serverTimestamp()forcreatedAt/updatedAt— never trust client-supplied timestamps. - Keep Security Rules DRY by extracting helper functions:
function isOwner(uid) { return request.auth.uid == uid; }. - Version Cloud Functions explicitly in
package.jsonand pinfirebase-adminandfirebase-functions. - Use
admin.auth().verifyIdToken()in HTTP functions to authenticate callers — never trust a UID passed in the request body. - Set
regionexplicitly on functions deployed to non-us-central1regions so client SDKs can connect. - Use environment config (
firebase functions:config:set) or Secret Manager (v2 functions) for secrets — never hardcode.
Do not
- Do not use
allow read, write: if true;in Security Rules even temporarily in a shared repo. - Do not store more than ~1 MB of data in a single Firestore document (hard limit is 1 MiB; practical limit is much lower to avoid slow reads).
- Do not use Firestore as a queue — use Cloud Tasks or Pub/Sub for background job delivery.
- Do not run Firestore queries without an equality/range filter on an indexed field; collection-group queries with no filter cause full-collection scans.
- Do not mix client SDK and admin SDK in the same runtime context.
- Do not use
onSnapshotlisteners in Cloud Functions (they run serverless and have no persistent connection).
Common mistakes to avoid
| Mistake | Consequence | Fix |
|---|---|---|
| Missing composite index on a multi-field query | Runtime error thrown to client | Add to firestore.indexes.json; deploy before the query goes live |
| Background-trigger infinite loop | Runaway invocations → billing shock | Guard with a state flag: check if the document field already has the expected value before writing |
Returning sensitive fields in Security Rules get() calls |
Data exfiltration via rule side-channel | Rules can call get() but the fetched data is only visible to the rule engine, not the client — still, limit get() usage to avoid extra reads |
Using collection.get() (fetch all docs) in a Cloud Function |
Memory exhaustion for large collections | Paginate with startAfter or use a Firestore aggregation query |
| Deploying functions without setting a minimum instance count for latency-sensitive paths | Cold-start delays of 2–5 s | Set minInstances: 1 on functions behind user-facing APIs |
Not initializing Firebase Admin with cert() in non-Google Cloud environments |
GOOGLE_APPLICATION_CREDENTIALS missing → auth failure |
Pass admin.initializeApp({ credential: admin.credential.cert(serviceAccount) }) |
Output format
firebase.json — hosting rewrites, function config
firestore.rules — Security Rules
firestore.indexes.json — composite indexes
storage.rules — Storage Security Rules
functions/
src/
index.ts — function exports
auth/ — auth trigger handlers
http/ — HTTP/callable handlers
firestore/ — document trigger handlers
package.json
.firebaserc — project aliases (default, staging, prod)
Cloud Function pattern:
export const createUserProfile = onCall({ region: "us-central1" }, async (request) => {
if (!request.auth) throw new HttpsError("unauthenticated", "Login required");
const uid = request.auth.uid;
await admin.firestore().doc(`profiles/${uid}`).set({ createdAt: FieldValue.serverTimestamp() });
return { success: true };
});
Related checklists
.claude/checklists/security.md.claude/checklists/database.md.claude/checklists/launch.md
Related agents
.claude/agents/engineering/backend-engineer.md.claude/agents/quality/security-auditor.md.claude/agents/core/system-analyst.md