f in x
Firestore Security Rules and Queries for a Performant and Protected NoSQL Database
> cd .. / HUB_EDITORIALE
Analisi dei dati e metriche

Firestore Security Rules and Queries for a Performant and Protected NoSQL Database

[2026-06-06] Author: Ing. Calogero Bono

Is your Firestore database slow? Are security rules a nightmare? Or worse, did you leave them as default and ended up with a high bill at the end of the month? If you work with Firebase, you know Firestore is powerful, but without well-written rules it becomes a pit of costs and vulnerabilities. At Meteora Web, we see it often in projects we take over: overly permissive (or missing) rules, inefficient queries that read thousands of unnecessary documents, or even data exposed to anyone.

Let's start with a technical premise many underestimate: Firestore is a document-oriented NoSQL database directly accessible from the client. There is no intermediate backend filtering data. Security rules are the only barrier between your data and a malicious user. In this guide, we explain how to set them correctly and how to write queries that are both efficient and covered by the rules, avoiding errors and waste.

Why Security Rules Are Not Optional

Imagine a bank with the front door wide open. Anyone would walk in and take whatever they want. That's exactly what a Firestore database without security rules is like. Many developers, in the rush to get a prototype working, set permissive rules and then forget about them. Result: anyone can read and write data, and you pay for every operation.

Common mistake: allow read, write: if true;. This is acceptable only during local development. Never in production. We've inherited projects with 100,000 reads a day from unauthorized bots. The bill added up to hundreds of euros per month.

Firestore security rules are based on three concepts:

  • match: defines the path (collection or document) the rules apply to.
  • allow: specifies the allowed operation (read, write, create, update, delete).
  • conditions: boolean expressions that check authentication, document fields, request state.

Basic Structure

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if false;
    }
  }
}

This rule blocks everything. Then we selectively open access based on our needs.

Rules Based on Authentication

The most common case: only authenticated users can read their own data. Use request.auth.uid to identify the user.

match /users/{userId} {
  allow read: if request.auth.uid == userId;
  allow write: if request.auth.uid == userId;
}

Here each user can read and write only their own document in the users collection. Note: this assumes the document ID is the user's UID. It's a best practice.

Rules Based on Document Fields

Often you need to check not only who accesses, but also what they are reading or writing. Example: a blog post has a field published: true. Only published posts should be readable by everyone, others only by the author.

match /posts/{postId} {
  allow read: if resource.data.published == true || request.auth.uid == resource.data.authorId;
  allow create: if request.auth.uid != null;
  allow update, delete: if request.auth.uid == resource.data.authorId;
}

Note: resource.data refers to the current document in the database, request.resource.data (only for writes) to the proposed document.

Efficient Queries and Related Security Rules

One of the most subtle errors: writing a rule that seems correct, but when the client runs a query, Firestore rejects it because the rule cannot guarantee that the query will return only allowed documents. Firestore evaluates the condition based on indexes and filters. If the query is not covered by the rule, you get a permission denied error, even if the document exists.

Example: suppose we have a messages collection in a chat, with fields roomId and participants (an array of UIDs). We want a user to see only messages from rooms they are part of.

match /messages/{messageId} {
  allow read: if request.auth.uid != null &&
    resource.data.participants.hasAny([request.auth.uid]);
}

If the client executes db.collection('messages').where('roomId', '==', 'room1'), the rule fails because it cannot know if messages in room1 have authorized participants. Firestore checks the index and the condition: the rule requires checking participants on every document, but the query does not filter on that field. So the rule is rejected.

Solution: the query must exactly mirror the rule's condition. Filter on participants or change the data structure. Alternatively, use getAfter to validate without exposing all documents.

Using getAfter to Validate Dependent Writes

If you need to check something in another document (e.g., user balance before an order), use getAfter(). Example: in an e-commerce app, before creating an order, check that the user has sufficient credit.

match /orders/{orderId} {
  allow create: if request.auth.uid == request.resource.data.userId &&
    getAfter(/databases/$(database)/documents/users/$(request.auth.uid)).data.balance >= request.resource.data.amount;
}

getAfter reads the database state after the write, useful to avoid extra reads on the client.

Practical Code Examples

Private Chat

Collection rooms with field members (array of UIDs).

match /rooms/{roomId} {
  allow read: if request.auth.uid != null && resource.data.members.hasAny([request.auth.uid]);
  allow create, update: if request.auth.uid != null && request.resource.data.members.hasAny([request.auth.uid]);
}

The client runs: db.collection('rooms').where('members', 'array-contains', auth.uid). This query is covered because the filter is on the same field used in the condition (members).

E-commerce – Only Admins Can Modify Prices

We want all authenticated users to read products, but only admins to modify them. Assume we have an admins collection with documents of UIDs.

match /products/{productId} {
  allow read: if true; // public
  allow write: if request.auth.uid != null && exists(/databases/$(database)/documents/admins/$(request.auth.uid));
}

exists incurs a read cost. Better to use custom claims: request.auth.token.admin == true if set via Firebase Admin SDK.

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

Custom claims are more performant because they don't require reads.

Tools to Test and Debug

Never trust rules written by eye. Use the Simulator in the Firebase console (under Firestore > Rules). You can simulate a request with an authenticated or anonymous user and see if it passes or fails. Even better: use the Emulator Suite locally. It lets you test rules, queries, and behavior without consuming real resources.

firebase emulators:start --only firestore,auth

Then connect your client to the emulator. We always use it during development: it speeds up the debug cycle and costs nothing.

Checklist for Secure Rules

  • Block everything by default: allow read, write: if false; for all collections.
  • Open selectively: match per collection, with conditions based on authentication and data.
  • Use custom claims for roles (admin, editor) instead of extra reads.
  • Ensure client queries match rule conditions (same filter fields).
  • Test with the Simulator at least key flows (login, read own data, write, denied access).
  • Never expose allow read, write: if true; in production.

In Summary — What to Do Now

  1. Go to the Firebase console, Firestore > Rules, and block all existing rules with if false.
  2. Identify your collections and use cases (user, admin, public).
  3. Write specific rules for each path, testing with the Simulator.
  4. For complex queries, make sure the client-side filter matches the rule condition.
  5. If you use roles, switch to custom claims: they are faster and cheaper.
  6. Enable the emulator suite for local development.

Do you already have rules in production? Check immediately for allow read, write: if true; or overly broad rules. An hour of verification today can prevent an unpleasant surprise on the next cost report.

Sponsored Protocol

Ing. Calogero Bono

> AUTHOR_EXTRACTED

Ing. Calogero Bono

Co-founder di Meteora Web. Ingegnere informatico, sviluppo ecosistemi digitali ad alte prestazioni. AI, automazione, SEO tecnica e infrastrutture web. Scrivo di tecnologia per rendere complesso… semplice.

[ Read Full Dossier ]

Hai bisogno di applicare questa strategia?

Esegui il protocollo di contatto per iniziare un progetto con noi.

> INIZIA_PROGETTO

Sponsored

> MW_JOURNAL

> READ_ALL()