Firestore security rules control who can read and write your data using a declarative DSL. Rules match document paths with wildcards and evaluate conditions using request.auth (the authenticated user), request.resource.data (incoming write data), and resource.data (existing document data). The most important patterns are request.auth != null for authenticated-only access, request.auth.uid == userId for user-scoped data, and data validation on writes. Rules are deployed with firebase deploy --only firestore:rules and take up to 10 minutes to propagate.
Setting Firestore Security Rules to Restrict Access
Firestore security rules are the server-side enforcement layer that protects your data. Without proper rules, anyone with your Firebase config can read or modify your entire database. This tutorial covers the rule syntax, common patterns for authentication, user ownership, role-based access, data validation, and how to test and deploy rules. Every production Firestore database needs well-crafted security rules.
Prerequisites
- A Firebase project with Firestore enabled
- Firebase CLI installed and logged in (npm install -g firebase-tools)
- Firebase Authentication configured with at least one sign-in method
- Understanding of your Firestore data structure (collections and document fields)
Step-by-step guide
Understand the rules structure and matching
Understand the rules structure and matching
Firestore rules are defined in a firestore.rules file. The top-level structure wraps all rules in a service declaration. Inside, match statements bind to document paths using wildcards. The {database} wildcard matches the database name, and {document=**} matches any document path recursively. Rules evaluate top-down — the most specific match wins. read permission splits into get (single document) and list (queries), and write splits into create, update, and delete.
1rules_version = '2';2service cloud.firestore {3 match /databases/{database}/documents {45 // This matches ALL documents (use sparingly)6 match /{document=**} {7 allow read, write: if false; // Default deny8 }910 // This matches documents in the 'posts' collection11 match /posts/{postId} {12 allow read: if true; // Public read13 allow write: if false; // No writes14 }1516 // This matches subcollection documents17 match /posts/{postId}/comments/{commentId} {18 allow read: if true;19 allow create: if request.auth != null;20 }21 }22}Expected result: All documents are denied by default. The posts collection is publicly readable but not writable. Comments are publicly readable and creatable by authenticated users.
Require authentication for all access
Require authentication for all access
The most fundamental rule checks request.auth != null to ensure the user is signed in. Apply this to collections that should only be accessible to logged-in users. You can combine auth checks with more specific conditions. The request.auth object contains uid, token (with email, custom claims), and provider information.
1rules_version = '2';2service cloud.firestore {3 match /databases/{database}/documents {45 // Authenticated users only6 match /profiles/{userId} {7 allow read: if request.auth != null;8 allow write: if request.auth != null9 && request.auth.uid == userId;10 }1112 // Require email verification13 match /premium/{docId} {14 allow read: if request.auth != null15 && request.auth.token.email_verified == true;16 }17 }18}Expected result: Only authenticated users can read profiles. Only the profile owner can write their profile. Premium content requires email verification.
Implement user ownership rules
Implement user ownership rules
For user-generated content, verify that the authenticated user owns the document. On create, check that the incoming data includes the correct author ID. On update and delete, check the existing document's author ID against the current user. This prevents users from modifying or deleting other users' content.
1rules_version = '2';2service cloud.firestore {3 match /databases/{database}/documents {4 match /posts/{postId} {5 // Anyone signed in can read6 allow read: if request.auth != null;78 // Only create if the authorId matches the current user9 allow create: if request.auth != null10 && request.resource.data.authorId == request.auth.uid;1112 // Only update/delete your own posts13 allow update: if request.auth != null14 && resource.data.authorId == request.auth.uid15 && request.resource.data.authorId == request.auth.uid;1617 allow delete: if request.auth != null18 && resource.data.authorId == request.auth.uid;19 }20 }21}Expected result: Users can only create posts with their own UID as authorId, can only update or delete their own posts, and cannot change the authorId field.
Validate incoming data on writes
Validate incoming data on writes
Use request.resource.data to validate the structure and content of incoming writes. Check field types with 'is string', 'is number', etc. Validate string lengths, number ranges, and required fields. This prevents malicious or malformed data from being written to your database. Combine validation with authentication and ownership checks.
1rules_version = '2';2service cloud.firestore {3 match /databases/{database}/documents {4 match /posts/{postId} {5 allow create: if request.auth != null6 && request.resource.data.authorId == request.auth.uid7 && request.resource.data.title is string8 && request.resource.data.title.size() > 09 && request.resource.data.title.size() <= 20010 && request.resource.data.body is string11 && request.resource.data.body.size() <= 1000012 && request.resource.data.createdAt == request.time13 && request.resource.data.keys().hasAll(['authorId', 'title', 'body', 'createdAt']);1415 allow update: if request.auth != null16 && resource.data.authorId == request.auth.uid17 && request.resource.data.diff(resource.data).affectedKeys()18 .hasOnly(['title', 'body', 'updatedAt']);19 }20 }21}Expected result: Documents can only be created with valid titles (1-200 chars), bodies (up to 10000 chars), and server timestamps. Updates can only modify title, body, and updatedAt.
Implement role-based access with custom claims
Implement role-based access with custom claims
For admin or moderator roles, use Firebase custom claims set via the Admin SDK. Access custom claims in rules through request.auth.token. Set custom claims server-side — they cannot be set from the client. Custom claims are stored in the user's ID token and are limited to 1000 bytes total.
1rules_version = '2';2service cloud.firestore {3 match /databases/{database}/documents {45 // Admin-only collection6 match /adminSettings/{docId} {7 allow read, write: if request.auth != null8 && request.auth.token.role == 'admin';9 }1011 // Posts: anyone reads, authors write, admins can delete any12 match /posts/{postId} {13 allow read: if request.auth != null;14 allow create: if request.auth != null15 && request.resource.data.authorId == request.auth.uid;16 allow update: if request.auth != null17 && resource.data.authorId == request.auth.uid;18 allow delete: if request.auth != null19 && (resource.data.authorId == request.auth.uid20 || request.auth.token.role == 'admin');21 }22 }23}Expected result: Admin settings are only accessible to users with the admin role claim. Admins can delete any post while regular users can only delete their own.
Test rules in the Rules Playground and deploy
Test rules in the Rules Playground and deploy
Before deploying, test your rules using the Rules Playground in Firebase Console. Navigate to Firestore > Rules > Edit rules and use the Playground tab to simulate read and write operations with different auth states. Once verified, deploy with the Firebase CLI. Rules take up to 1 minute for new queries and up to 10 minutes for active listeners to pick up changes.
1# Deploy only Firestore rules2firebase deploy --only firestore:rules34# Test rules with the emulator5firebase emulators:start --only firestore67# Run automated rule tests (with @firebase/rules-unit-testing)8npm testExpected result: Rules are deployed and take effect within minutes. The Rules Playground confirms that access is correctly restricted.
Complete working example
1rules_version = '2';2service cloud.firestore {3 match /databases/{database}/documents {45 // Helper function: is the user signed in?6 function isSignedIn() {7 return request.auth != null;8 }910 // Helper function: does the user own this document?11 function isOwner(userId) {12 return isSignedIn() && request.auth.uid == userId;13 }1415 // Helper function: is the user an admin?16 function isAdmin() {17 return isSignedIn() && request.auth.token.role == 'admin';18 }1920 // User profiles: owner reads/writes, others can read21 match /profiles/{userId} {22 allow read: if isSignedIn();23 allow create, update: if isOwner(userId)24 && request.resource.data.displayName is string25 && request.resource.data.displayName.size() > 026 && request.resource.data.displayName.size() <= 50;27 allow delete: if isOwner(userId) || isAdmin();28 }2930 // Posts: public read, author writes, admin moderates31 match /posts/{postId} {32 allow read: if isSignedIn();33 allow create: if isSignedIn()34 && request.resource.data.authorId == request.auth.uid35 && request.resource.data.title is string36 && request.resource.data.title.size() > 037 && request.resource.data.title.size() <= 20038 && request.resource.data.body is string39 && request.resource.data.body.size() <= 10000;40 allow update: if isSignedIn()41 && resource.data.authorId == request.auth.uid;42 allow delete: if isSignedIn()43 && (resource.data.authorId == request.auth.uid || isAdmin());4445 // Post comments46 match /comments/{commentId} {47 allow read: if isSignedIn();48 allow create: if isSignedIn()49 && request.resource.data.authorId == request.auth.uid50 && request.resource.data.text is string51 && request.resource.data.text.size() > 052 && request.resource.data.text.size() <= 2000;53 allow delete: if isSignedIn()54 && (resource.data.authorId == request.auth.uid || isAdmin());55 }56 }5758 // Admin settings: admin only59 match /settings/{docId} {60 allow read, write: if isAdmin();61 }6263 // Default deny64 match /{document=**} {65 allow read, write: if false;66 }67 }68}Common mistakes when setting Firestore Rules to Restrict Access
Why it's a problem: Using 'allow read, write: if true' in production, leaving the entire database open to anyone
How to avoid: Never use unconditional allow rules in production. Start with 'if false' as the default and add specific conditions for each collection.
Why it's a problem: Thinking security rules act as filters — they reject entire queries that could return unauthorized documents
How to avoid: Firestore evaluates whether a query could potentially return unauthorized documents based on its constraints. Your query must include filters that match your rules. For example, if rules require authorId == request.auth.uid, your query must include where('authorId', '==', currentUser.uid).
Why it's a problem: Forgetting to deploy rules after editing them in firestore.rules, leaving old rules in production
How to avoid: Run firebase deploy --only firestore:rules after every rules change. Consider adding this to your CI/CD pipeline so rules are always deployed with code changes.
Why it's a problem: Not validating incoming data fields on create, allowing malformed documents in the database
How to avoid: Add type checks (is string, is number), size constraints, and required field checks to all create and update rules.
Best practices
- Start with a default deny rule (allow read, write: if false) and explicitly allow access for each collection
- Use helper functions (isSignedIn, isOwner, isAdmin) to keep rules DRY and readable
- Always validate incoming data types and sizes on create and update operations
- Use request.resource.data.diff(resource.data).affectedKeys().hasOnly() to restrict which fields can be updated
- Set custom claims for role-based access rather than checking a Firestore roles document (which adds a read per request)
- Test rules with the Rules Playground and automate testing with @firebase/rules-unit-testing in CI
- Deploy rules as part of your CI/CD pipeline to ensure they stay in sync with your application code
- Remember that rules are not filters — your queries must include constraints that match your rules
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I have a Firestore database with users, posts, and comments collections. Write production-ready security rules that require authentication for all access, let users only modify their own data, validate post titles (1-200 chars) and bodies (max 10000 chars), support admin role via custom claims, and include helper functions for common checks.
Generate a complete Firestore security rules file for a blog application with: user profiles (owner read/write), posts (public read, author write, admin delete), nested comments (auth create, author/admin delete), admin settings (admin only), and a default deny rule. Include helper functions for isSignedIn, isOwner, isAdmin, and data validation.
Frequently asked questions
Do Firestore security rules filter query results?
No. Security rules are not filters. Firestore evaluates whether a query could potentially return unauthorized documents. If it could, the entire query is rejected, even if all actual results would be authorized. Your query constraints must match what your rules allow.
How long do rules take to propagate after deployment?
New rules take effect within about 1 minute for new queries. Active real-time listeners may take up to 10 minutes to use the new rules. During deployment, there is no downtime — old rules remain active until new ones propagate.
Can I use Firestore data in security rules (like checking a roles document)?
Yes. Use get() and exists() functions to read other documents in rules. For example: get(/databases/$(database)/documents/roles/$(request.auth.uid)).data.isAdmin. However, each get() counts as a document read and adds latency. Custom claims are faster and free.
What is the maximum complexity of security rules?
Security rules support custom functions with up to 10 let bindings and a call stack depth of 10. Each rule evaluation can make up to 10 get() and exists() calls. Rules are limited to 256 KB in size.
Should I use custom claims or Firestore documents for roles?
Use custom claims for roles checked in security rules. Claims are stored in the auth token (no extra read), limited to 1000 bytes, and set server-side. Use Firestore documents for complex role structures with many permissions, but be aware each rules get() call adds latency and a document read.
Can RapidDev help design and implement Firestore security rules for my application?
Yes. RapidDev can design security rules tailored to your data model, including role-based access, data validation, and automated testing to ensure your database is properly secured.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation