Skilllibrary firebase-rules
git clone https://github.com/merceralex397-collab/skilllibrary
T=$(mktemp -d) && git clone --depth=1 https://github.com/merceralex397-collab/skilllibrary "$T" && mkdir -p ~/.claude/skills && cp -r "$T/09-backend-api-and-data/firebase-rules" ~/.claude/skills/merceralex397-collab-skilllibrary-firebase-rules && rm -rf "$T"
09-backend-api-and-data/firebase-rules/SKILL.mdPurpose
Write, test, and deploy Firestore and Cloud Storage security rules. This skill covers the full lifecycle: authoring rules with correct syntax, testing them locally with the Firebase Emulator Suite, and deploying with
firebase deploy --only firestore:rules or firebase deploy --only storage.
When to use this skill
Use this skill when:
- writing or modifying
orfirestore.rules
filesstorage.rules - implementing access control patterns (owner-only, role-based, field-level validation)
- setting up
test suites@firebase/rules-unit-testing - debugging why a read/write is being denied in the emulator or production
- reviewing rules for security gaps before deployment
- adding data validation logic inside security rules (type checks, field constraints)
Do not use this skill when
- the task is about Firestore SDK usage (queries, listeners, CRUD) — use
insteadfirebase-sdk - the task is about Cloud Functions triggers or Admin SDK initialization — use
insteadfirebase-sdk - the task is about BigQuery export or analytics — use
insteadbigquery - the task is purely about data model design — use
insteaddata-model
Operating procedure
-
Identify the rules file and version. Confirm
is at the top. Version 2 is required for collection group queries and recursive wildcards (rules_version = '2';
).{document=**} -
Map the access pattern to match paths. Structure
blocks to mirror the Firestore document hierarchy. Use specific paths (match
) before wildcards./users/{userId}match /databases/{database}/documents { match /users/{userId} { allow read: if request.auth != null && request.auth.uid == userId; allow write: if request.auth != null && request.auth.uid == userId; } } -
Choose the right granularity. Split
intoread
andget
. Splitlist
intowrite
,create
, andupdate
. This prevents users from listing all documents when they should only get their own.delete -
Add data validation for writes. Use
(the incoming document) to validate fields onrequest.resource.data
andcreate
:updateallow create: if request.resource.data.keys().hasAll(['name', 'email']) && request.resource.data.name is string && request.resource.data.name.size() > 0 && request.resource.data.name.size() <= 100; -
Use helper functions for reuse. Extract common checks into functions at the top of the rules file:
function isOwner(userId) { return request.auth != null && request.auth.uid == userId; } function hasRole(role) { return request.auth != null && request.auth.token[role] == true; } -
Start the emulator and run tests.
firebase emulators:start --only firestore # In another terminal: npm test # runs @firebase/rules-unit-testing suite -
Write rule unit tests. Each test should authenticate as a specific user context and assert allow/deny:
import { initializeTestEnvironment, assertSucceeds, assertFails } from '@firebase/rules-unit-testing'; const testEnv = await initializeTestEnvironment({ projectId: 'my-project', firestore: { rules: fs.readFileSync('firestore.rules', 'utf8') }, }); // Should allow owner to read their own doc const ownerCtx = testEnv.authenticatedContext('user123'); await assertSucceeds(getDoc(doc(ownerCtx.firestore(), 'users/user123'))); // Should deny other users const otherCtx = testEnv.authenticatedContext('other-user'); await assertFails(getDoc(doc(otherCtx.firestore(), 'users/user123'))); -
Deploy rules. After tests pass:
firebase deploy --only firestore:rules firebase deploy --only storage # if storage rules changed -
Verify in production. Check the Firebase Console → Firestore → Rules tab to confirm the deployed version matches. Monitor the Rules Evaluation dashboard for unexpected denies.
Decision rules
- Always use
— version 1 lacks recursive wildcards and collection group support.rules_version = '2' - Prefer
+get
overlist
andread
+create
+update
overdelete
for fine-grained control.write - Validate incoming data shape on
; validate only changed fields oncreate
.update - Use
(custom claims) for role-based access, not a Firestore document lookup from rules (rules cannot read other documents efficiently in all cases — userequest.auth.token
sparingly as it counts against the 10-call limit per evaluation).get() - Never use
orallow read: if true
in production.allow write: if true - Keep rules under ~40KB and nesting under 10 levels to avoid evaluation limits.
- For Storage rules, validate
andrequest.resource.contentType
on uploads.request.resource.size
Output requirements
— the modifiedRules File
file with correct match paths and conditions.rules
— unit tests covering allow and deny paths for each ruleTest Coverage
— the exactDeployment Command
command to runfirebase deploy
— a summary of who can do what on which pathsAccess Matrix
References
Read these when working on specific aspects:
— common rule patterns with code examplesreferences/implementation-patterns.md
— pre-deployment verification stepsreferences/validation-checklist.md
— debugging denies and common mistakesreferences/failure-modes.md
Related skills
— client/server SDK usage for Firestore, Auth, Storagefirebase-sdk
— Firestore document/collection schema designdata-model
— API boundary contracts that rules help enforceapi-contracts
Anti-patterns
- God rule at root level. Placing
at the database root and "planning to tighten later." This never happens and exposes all data.allow read, write: if true - Checking auth in some rules but not others. Inconsistent auth checks across sibling match blocks leave gaps. Use a helper function.
- Using
when you meanresource.data
.request.resource.data
is the existing document;resource.data
is the incoming data on writes. Confusing them causes rules to reference wrong fields.request.resource.data - Not testing deny paths. Only testing that valid users can access data. You must also test that invalid users, unauthenticated users, and malformed data are denied.
- Deeply nested recursive wildcards. Using
at a high level overrides specific match blocks below it. Place recursive wildcards only where you truly need catch-all behavior.{document=**} - Relying on client-side filtering. Rules are not filters — a query that could return unauthorized documents will fail entirely, even if the user has access to some of those documents. Structure queries to match rule boundaries.
Failure handling
- Silent deny with no error detail. Firebase rules deny silently. Use the Emulator's debug logging (
) andfirebase emulators:start --debug
to identify which condition failed. In production, check the Rules Evaluation metrics in the Firebase Console.@firebase/rules-unit-testing - Rules not deployed after update. If you edited
but forgot to deploy, production still runs the old version. Always deploy after changes and verify the timestamp in the Console.firestore.rules - Emulator rules out of sync. The emulator reads rules on startup. If you edit rules after starting the emulator, restart it or use
with the latest rules.--import - Evaluation limit exceeded. Firestore rules have a limit of ~1000 expressions per evaluation and 10
/get()
calls. Refactor complex conditions into helper functions and minimize cross-document lookups.exists() - Custom claims not propagated. After setting custom claims via Admin SDK, the user must refresh their ID token (sign out/in or
) before rules see the new claims.getIdToken(true)