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 .firebaserc and firebase.json; gitignore functions/node_modules.
- Write Security Rules early — create
firestore.rules with deny-all as the default, then open exactly what each user role needs. Mirror the same pattern for storage.rules.
- Test rules with the Emulator — run
firebase emulators:start and point the SDK at localhost:8080 (Firestore) / localhost:9099 (Auth). Use @firebase/rules-unit-testing for 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
onCall for client-invoked logic (handles Auth context automatically). Use onDocumentWritten/onDocumentCreated for 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 hosting to limit blast radius.
- Before production: run
firebase firestore:indexes to confirm indexes are deployed; check the Firebase console → Usage tab for quota headroom.
Standards
Do
- Use
FieldValue.serverTimestamp() for createdAt/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.json and pin firebase-admin and firebase-functions.
- Use
admin.auth().verifyIdToken() in HTTP functions to authenticate callers — never trust a UID passed in the request body.
- Set
region explicitly on functions deployed to non-us-central1 regions 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
onSnapshot listeners 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
1---2name: firebase3description: Use when the project uses Firebase — Firestore, Firebase Auth, Storage, Cloud Functions, Hosting, Realtime Database, Remote Config — including security rules, schemas, Emulators.4---56# Firebase Skill78## When to use910- Architecting Firestore collection/subcollection structure and indexes11- Writing and testing Firestore Security Rules12- Implementing Firebase Auth (email/password, OAuth, custom tokens, custom claims)13- Writing Cloud Functions for Firebase (v1 HTTP, callable, background triggers)14- Configuring Firebase Hosting with rewrite rules to Cloud Functions or Cloud Run15- Setting up the Firebase Emulator Suite for local development and CI16- Diagnosing quota/billing issues, missing indexes, or security-rule denial errors1718---1920## Workflow21221. **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.232. **Initialize the project**: `firebase init` → select Firestore, Functions, Hosting, Emulators. Commit `.firebaserc` and `firebase.json`; gitignore `functions/node_modules`.243. **Write Security Rules early** — create `firestore.rules` with deny-all as the default, then open exactly what each user role needs. Mirror the same pattern for `storage.rules`.254. **Test rules with the Emulator** — run `firebase emulators:start` and point the SDK at `localhost:8080` (Firestore) / `localhost:9099` (Auth). Use `@firebase/rules-unit-testing` for automated rule tests.265. **Add composite indexes via `firestore.indexes.json`** — never create them only in the console; indexes defined in the file are reproducible across environments.276. **Cloud Functions**: keep each function focused on a single trigger. Use `onCall` for client-invoked logic (handles Auth context automatically). Use `onDocumentWritten`/`onDocumentCreated` for server-side side effects. Avoid chaining background triggers (trigger → function → write → trigger) as this creates infinite loops.287. **Deploy incrementally**: `firebase deploy --only firestore:rules`, then `--only functions`, then `--only hosting` to limit blast radius.298. **Before production**: run `firebase firestore:indexes` to confirm indexes are deployed; check the Firebase console → Usage tab for quota headroom.3031---3233## Standards3435### Do36- Use `FieldValue.serverTimestamp()` for `createdAt`/`updatedAt` — never trust client-supplied timestamps.37- Keep Security Rules DRY by extracting helper functions: `function isOwner(uid) { return request.auth.uid == uid; }`.38- Version Cloud Functions explicitly in `package.json` and pin `firebase-admin` and `firebase-functions`.39- Use `admin.auth().verifyIdToken()` in HTTP functions to authenticate callers — never trust a UID passed in the request body.40- Set `region` explicitly on functions deployed to non-`us-central1` regions so client SDKs can connect.41- Use environment config (`firebase functions:config:set`) or Secret Manager (v2 functions) for secrets — never hardcode.4243### Do not44- Do not use `allow read, write: if true;` in Security Rules even temporarily in a shared repo.45- 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).46- Do not use Firestore as a queue — use Cloud Tasks or Pub/Sub for background job delivery.47- Do not run Firestore queries without an equality/range filter on an indexed field; collection-group queries with no filter cause full-collection scans.48- Do not mix client SDK and admin SDK in the same runtime context.49- Do not use `onSnapshot` listeners in Cloud Functions (they run serverless and have no persistent connection).5051---5253## Common mistakes to avoid5455| Mistake | Consequence | Fix |56|---|---|---|57| Missing composite index on a multi-field query | Runtime error thrown to client | Add to `firestore.indexes.json`; deploy before the query goes live |58| 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 |59| 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 |60| Using `collection.get()` (fetch all docs) in a Cloud Function | Memory exhaustion for large collections | Paginate with `startAfter` or use a Firestore aggregation query |61| 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 |62| 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) })` |6364---6566## Output format6768```69firebase.json — hosting rewrites, function config70firestore.rules — Security Rules71firestore.indexes.json — composite indexes72storage.rules — Storage Security Rules73functions/74 src/75 index.ts — function exports76 auth/ — auth trigger handlers77 http/ — HTTP/callable handlers78 firestore/ — document trigger handlers79 package.json80.firebaserc — project aliases (default, staging, prod)81```8283Cloud Function pattern:84```ts85export const createUserProfile = onCall({ region: "us-central1" }, async (request) => {86 if (!request.auth) throw new HttpsError("unauthenticated", "Login required");87 const uid = request.auth.uid;88 await admin.firestore().doc(`profiles/${uid}`).set({ createdAt: FieldValue.serverTimestamp() });89 return { success: true };90});91```9293---9495## Related checklists96- `.claude/checklists/security.md`97- `.claude/checklists/database.md`98- `.claude/checklists/launch.md`99100## Related agents101- `.claude/agents/engineering/backend-engineer.md`102- `.claude/agents/quality/security-auditor.md`103- `.claude/agents/core/system-analyst.md`