How to Write Firestore Rules

How to Write Firestore Rules Google Firestore is a powerful, scalable NoSQL document database built for modern mobile and web applications. Its real-time synchronization, offline support, and flexible data structure make it a top choice for developers. However, without proper security rules, even the most robust application can be vulnerable to data breaches, unauthorized access, or malicious writ

Nov 10, 2025 - 09:00
Nov 10, 2025 - 09:00
 0

How to Write Firestore Rules

Google Firestore is a powerful, scalable NoSQL document database built for modern mobile and web applications. Its real-time synchronization, offline support, and flexible data structure make it a top choice for developers. However, without proper security rules, even the most robust application can be vulnerable to data breaches, unauthorized access, or malicious writes. Writing effective Firestore rules is not optional—it’s essential. These rules define who can read from or write to your database, under what conditions, and with what constraints. Mastering Firestore rules ensures your data remains secure, your users stay protected, and your application performs reliably at scale.

This comprehensive guide walks you through everything you need to know to write secure, efficient, and maintainable Firestore rules—from foundational syntax to advanced patterns and real-world examples. Whether you’re a beginner setting up your first project or an experienced developer refining production rules, this tutorial provides actionable insights backed by best practices and industry standards.

Step-by-Step Guide

Understanding Firestore Rule Structure

Firestore rules are written in a domain-specific language called the Firestore Security Rules Language. These rules are deployed as a single configuration file that applies to your entire database. The structure is hierarchical and declarative, meaning you define permissions based on conditions rather than procedural logic.

The basic syntax of a Firestore rule consists of three core components:

  • Service: Declares the service you’re securing—in this case, firestore.
  • Match: Defines the path to the documents or collections you’re protecting.
  • Allow: Specifies the operations (read, write, create, update, delete) and the conditions under which they are permitted.

Here’s a minimal example:

service cloud.firestore {

match /databases/{database}/documents {

match /users/{userId} {

allow read, write: if request.auth != null;

}

}

}

In this example, any authenticated user can read or write to their own user document under the /users/{userId} path. The {userId} is a wildcard that captures the document ID, making the rule reusable across all user documents.

Setting Up Your Firestore Project

Before writing rules, ensure your Firestore database is properly configured:

  1. Log in to the Firebase Console.
  2. Select your project or create a new one.
  3. Navigate to the Firestore Database section.
  4. Click on the Rules tab.
  5. By default, new projects start in test mode, where rules allow unrestricted access: allow read, write: if true;. This is suitable only for development.
  6. Switch to locked mode by replacing the default rules with your own secure configuration before deploying to production.

Always test your rules in the Firebase Emulator Suite before deploying. This allows you to simulate requests and validate behavior without affecting live data.

Writing Your First Secure Rule

Let’s build a simple rule set for a blog application with two collections: posts and comments.

Requirements:

  • Anyone can read published posts.
  • Only authenticated users can create or update posts.
  • Only the post author can delete a post.
  • Anyone can read comments.
  • Only authenticated users can write comments.
  • Users can only edit their own comments.

Here’s the rule set that fulfills these requirements:

service cloud.firestore {

match /databases/{database}/documents {

// Posts collection

match /posts/{postId} {

allow read: if true; // Public read access

allow write: if request.auth != null; // Authenticated users only

allow delete: if request.auth != null && request.auth.uid == resource.data.authorId;

}

// Comments collection

match /comments/{commentId} {

allow read: if true; // Public read access

allow write: if request.auth != null; // Only authenticated users

allow update: if request.auth != null && request.auth.uid == resource.data.userId;

allow delete: if request.auth != null && request.auth.uid == resource.data.userId;

}

}

}

Key elements explained:

  • request.auth != null: Ensures the user is authenticated.
  • request.auth.uid: The unique identifier of the authenticated user.
  • resource.data: Represents the data of the document before the write operation.
  • resource: The document being accessed or modified.

Notice how we separate write from delete and update. This allows fine-grained control. For example, a user may be allowed to update a post but not delete it, depending on your business logic.

Using Wildcards and Path Variables

Wildcards like {userId} or {postId} capture dynamic segments of a document path. These are crucial for writing reusable, scalable rules.

Example: Restricting access to user-specific data

match /users/{userId} {

allow read, write: if request.auth != null && request.auth.uid == userId;

}

This rule ensures that a user can only access their own document under the /users collection. The wildcard {userId} is referenced in the condition as userId (without braces).

Wildcards can also be nested:

match /users/{userId}/profile/{profileId} {

allow read: if request.auth.uid == userId;

}

This rule applies to documents nested under a user’s profile, such as /users/abc123/profile/bio. The condition ensures only the owner of the user document can read their profile data.

Validating Data with Request and Resource Objects

Firestore rules allow you to validate both incoming data (request.resource.data) and existing data (resource.data). This is critical for enforcing data integrity.

Example: Enforce that a post must have a title and content

match /posts/{postId} {

allow create: if request.auth != null &&

request.resource.data.title is string &&

request.resource.data.title != "" &&

request.resource.data.content is string &&

request.resource.data.content != "" &&

request.resource.data.publishedAt is timestamp;

}

Here, we validate:

  • The title and content fields exist and are non-empty strings.
  • The publishedAt field is a valid timestamp.

For updates, compare incoming data with existing data to prevent unintended changes:

match /posts/{postId} {

allow update: if request.auth != null &&

request.auth.uid == resource.data.authorId &&

request.resource.data.title == resource.data.title && // Title cannot change

request.resource.data.content is string &&

request.resource.data.content.size() > 0;

}

This rule allows updating content but prevents modification of the title—a common requirement in content management systems.

Working with Custom Claims and Roles

For applications with role-based access control (RBAC), Firebase Authentication supports custom claims. These are key-value pairs attached to a user’s token and can be accessed in Firestore rules via request.auth.token.

Example: Assigning an admin role

On the server side (using Firebase Admin SDK), you might set a claim:

admin.auth().setCustomUserClaims(uid, { admin: true });

In Firestore rules:

match /admin/{documentId} {

allow read, write: if request.auth.token.admin == true;

}

Custom claims are cached in the user’s token, so changes may take up to an hour to reflect. For immediate updates, force token refresh on the client side after setting claims.

Testing Rules in the Emulator

Before deploying rules to production, test them using the Firebase Emulator Suite:

  1. Install the Firebase CLI: npm install -g firebase-tools
  2. Initialize your project: firebase init
  3. Select Firestore and Emulator Suite.
  4. Start the emulators: firebase emulators:start
  5. Use the Emulator UI at http://localhost:4000 to simulate reads and writes.

Write unit tests using the Firebase Rules Testing Library:

const { assertSucceeds, assertFails } = require("@firebase/rules-unit-testing");

describe("Firestore Rules", () => {

const testUserId = "test-user-123";

beforeEach(async () => {

await loadFirestoreRules({

projectId: "test-project",

rules: fs.readFileSync("firestore.rules", "utf8")

});

});

it("allows authenticated users to read their own document", async () => {

const db = getFirestore(testUserId);

const doc = db.collection("users").doc(testUserId);

await assertSucceeds(doc.set({ name: "John" }));

});

it("denies unauthenticated users from reading", async () => {

const db = getFirestore(); // Unauthenticated

const doc = db.collection("users").doc("any-user");

await assertFails(doc.get());

});

});

Testing ensures your rules behave as expected under various scenarios and reduces the risk of security gaps.

Best Practices

Principle of Least Privilege

Always grant the minimum level of access required. Avoid using allow read, write: if true; in production. Even if your app is internal, assume data can be accessed by malicious actors. Restrict access by user identity, document ownership, or role—never by default.

Use Explicit Conditions

Never rely on implicit assumptions. For example, don’t assume that if a user can write to a document, they can delete it. Be explicit:

allow delete: if request.auth.uid == resource.data.ownerId;

Explicit conditions are easier to audit, debug, and maintain.

Organize Rules by Collection

Structure your rules in a logical hierarchy. Group related collections together and use nested match statements to avoid repetition:

match /users/{userId} {

allow read, write: if request.auth.uid == userId;

match /posts/{postId} {

allow read: if true;

allow write: if request.auth.uid == userId;

}

match /settings/{settingId} {

allow read, write: if request.auth.uid == userId;

}

}

This structure improves readability and reduces redundancy.

Avoid Deep Nesting

While Firestore allows deeply nested documents, complex rule paths can become hard to maintain. Prefer flat structures with reference fields when possible. For example, instead of:

/users/{userId}/posts/{postId}/comments/{commentId}/replies/{replyId}

Consider:

/posts/{postId}/comments/{commentId}

/comments/{commentId}/replies/{replyId}

This simplifies rule logic and improves performance.

Validate All Input

Always validate the shape, type, and content of incoming data. Malicious clients can send malformed payloads. Use is string, is number, is boolean, is timestamp, and size() to enforce constraints:

request.resource.data.email is string &&

request.resource.data.email.matches("^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$")

Regular expressions are supported for validating formats like emails, phone numbers, or slugs.

Use Functions for Reusability

Firestore rules support custom functions to avoid repetition. Define reusable logic at the top of your rules file:

function isOwner() {

return request.auth != null && request.auth.uid == resource.data.userId;

}

function isEmailVerified() {

return request.auth.token.email_verified == true;

}

match /users/{userId} {

allow read, write: if isOwner() && isEmailVerified();

}

Functions improve maintainability and make rules more readable.

Monitor and Log Access Patterns

Use Firebase Analytics and Cloud Logging to monitor unusual access patterns. Set up alerts for high volumes of denied requests, which may indicate brute-force attacks or misconfigured clients.

Never Trust Client-Side Data

Firestore rules are the final gatekeeper. Even if your app validates data on the frontend, malicious users can bypass client-side checks. All validation must occur in the rules layer.

Keep Rules Versioned

Treat your rules like code. Store them in your version control system (e.g., Git). Use meaningful commit messages:

  • “Add comment deletion restriction”
  • “Fix: Allow admin to update any post”

Deploy rules alongside your application code to ensure consistency between backend logic and security policies.

Review Rules Regularly

As your app evolves, so should your rules. Schedule quarterly reviews to:

  • Remove unused rules
  • Update permissions for new features
  • Remove overly permissive conditions
  • Verify custom claims are still relevant

Tools and Resources

Firebase Console Rules Editor

The Firebase Console provides a built-in editor for writing and testing rules. It includes syntax highlighting, real-time validation, and a simulation panel. While convenient, it lacks advanced testing capabilities—use the Emulator Suite for comprehensive validation.

Firebase Emulator Suite

The Firebase Emulator Suite is the most critical tool for rule development. It runs a local instance of Firestore, Authentication, and other services, allowing you to:

  • Simulate authenticated and unauthenticated users
  • Test edge cases without affecting live data
  • Run automated unit tests
  • Inspect data changes in real time

Install via npm: npm install -g firebase-tools

Firestore Rules Testing Library

This Node.js library enables automated testing of Firestore rules. It integrates with Jest and Mocha and allows you to write assertions like assertSucceeds() and assertFails().

GitHub: firebase/rules-unit-testing

Firestore Rules Linter (firestore-rules-lint)

A community-developed tool that checks your rules for common anti-patterns, such as overly broad permissions or missing validations.

Install: npm install -g firestore-rules-lint

Run: firestore-rules-lint firestore.rules

VS Code Extensions

Enhance your development experience with extensions:

  • Firebase Snippets: Auto-complete common rule patterns.
  • Firestore Rules Syntax Highlighting: Improves readability.
  • FireRules: Provides linting and validation within the editor.

Documentation and Community

Third-Party Tools

  • Fireproof: A visual rule editor and simulator for complex rule sets.
  • RuleGuard: Monitors your production rules for changes and alerts on potential vulnerabilities.

Real Examples

Example 1: Social Media App

Requirements:

  • Users can follow other users.
  • Posts are visible to followers and the author.
  • Only the post author can delete their post.
  • Comments are public but only the commenter can edit.

Rules:

service cloud.firestore {

match /databases/{database}/documents {

// Users collection

match /users/{userId} {

allow read: if true; // Public profile

allow write: if request.auth != null && request.auth.uid == userId;

}

// Follows collection (many-to-many relationship)

match /follows/{followerId}/{followingId} {

allow read: if request.auth != null && (request.auth.uid == followerId || request.auth.uid == followingId);

allow write: if request.auth != null && request.auth.uid == followerId;

}

// Posts collection

match /posts/{postId} {

allow read: if request.auth != null && (

resource.data.authorId == request.auth.uid || // Author can read

exists(/databases/$(database)/documents/follows/$(request.auth.uid)/$(resource.data.authorId)) // Follower can read

);

allow write: if request.auth != null && request.auth.uid == resource.data.authorId;

allow delete: if request.auth != null && request.auth.uid == resource.data.authorId;

}

// Comments collection

match /comments/{commentId} {

allow read: if true; // Public comments

allow write: if request.auth != null;

allow update: if request.auth != null && request.auth.uid == resource.data.userId;

allow delete: if request.auth != null && request.auth.uid == resource.data.userId;

}

}

}

Example 2: E-Commerce Product Catalog

Requirements:

  • Products are publicly readable.
  • Only store admins can create, update, or delete products.
  • Customers can read their own orders.
  • Only the system can create orders (no client writes).

Rules:

service cloud.firestore {

match /databases/{database}/documents {

// Products - public

match /products/{productId} {

allow read: if true;

allow write: if request.auth.token.admin == true;

}

// Orders - server-only creation

match /orders/{orderId} {

allow read: if request.auth != null && request.auth.uid == resource.data.userId;

allow create: if false; // Only server can create via Admin SDK

allow update: if false; // Orders are immutable

allow delete: if false;

}

// Order items

match /orders/{orderId}/items/{itemId} {

allow read: if request.auth != null && request.auth.uid == resource.data.userId;

allow write: if false;

}

}

}

Important: Order creation is handled server-side using the Firebase Admin SDK, ensuring clients cannot manipulate pricing or inventory.

Example 3: Private Messaging System

Requirements:

  • Messages are only visible to participants in a conversation.
  • Users can only send messages to conversations they’re part of.
  • Messages cannot be edited or deleted.

Rules:

service cloud.firestore {

match /databases/{database}/documents {

// Conversations

match /conversations/{conversationId} {

allow read: if request.auth != null &&

(request.auth.uid in resource.data.participants);

allow write: if false; // Only system creates conversations

}

// Messages

match /conversations/{conversationId}/messages/{messageId} {

allow read: if request.auth != null &&

(request.auth.uid in get(/databases/$(database)/documents/conversations/$(conversationId)).data.participants);

allow create: if request.auth != null &&

request.auth.uid in get(/databases/$(database)/documents/conversations/$(conversationId)).data.participants;

allow update: if false;

allow delete: if false;

}

}

}

Here, we use the get() function to fetch the parent document’s data and verify the user is a participant. This ensures messages are only accessible to those in the conversation.

FAQs

Can Firestore rules prevent data deletion by admins?

No. Firebase Admin SDK operations bypass Firestore security rules entirely. Admins have full access to all data. If you need to restrict admin actions, implement application-level controls or audit logs, not Firestore rules.

Do Firestore rules support OR conditions?

Yes. Use the || operator. Example: if request.auth.uid == resource.data.ownerId || request.auth.token.admin == true

Can I use Firestore rules to limit the number of documents a user can create?

Not directly. Firestore rules cannot count documents in a collection. To enforce limits, use Cloud Functions to validate document counts before allowing writes, or use a counter document updated on each creation.

Why does my rule work in the emulator but fail in production?

Common causes:

  • Custom claims haven’t propagated (cached tokens).
  • Time drift between client and server (ensure device clocks are accurate).
  • Rules syntax errors not caught locally.
  • Missing index errors (rules and queries must align).

Always test with the same authentication state and data structure in both environments.

Are Firestore rules case-sensitive?

Yes. Field names, path segments, and string comparisons are case-sensitive. emailEmail.

How do I handle anonymous users?

Anonymous users have a valid request.auth.uid but no email or provider info. You can allow access to them using request.auth != null. However, consider converting them to permanent accounts after they perform key actions.

Can I use Firestore rules to restrict access by IP address?

No. Firestore rules do not have access to client IP addresses. For IP-based restrictions, use a Cloud Function as a proxy or implement a firewall at the network level.

What happens if I deploy broken rules?

Firestore rules are deployed atomically. If your rules contain syntax errors, the deployment fails and the previous rules remain active. Always test locally before deploying.

How do I reset Firestore rules to default?

In the Firebase Console, click “Reset Rules” in the Rules tab. This reverts to test mode: allow read, write: if true;. Never use this in production.

Conclusion

Writing secure and scalable Firestore rules is not a one-time task—it’s an ongoing discipline that must evolve with your application. The rules you write today will protect your data tomorrow, and the care you take in crafting them reflects the integrity of your entire system. By following the principles of least privilege, validating every input, organizing your rules logically, and testing relentlessly, you build not just a database, but a trustworthy foundation for your users.

Remember: security is not a feature. It’s the baseline. Whether you’re building a social platform, an e-commerce store, or a private messaging app, your users expect their data to be safe. Firestore rules give you the tools to deliver on that promise. Use them wisely, test them thoroughly, and review them regularly.

As you continue to develop, revisit this guide. Bookmark the official documentation. Engage with the community. And never assume—always verify. With the right approach, Firestore rules become not a burden, but a powerful ally in your quest to build secure, reliable, and user-centric applications.