Customize Cloud Firestore Security Rules

Cloud Firestore Security Rules are used to determine who has read and write access to data stored in Cloud Firestore, provide schema validation and referential integrity, and enforce business logic constraints.

Working with hierarchical data

Cloud Firestore stores its data in a hierarchy of collections, documents, and subcollections, and you'll often be working to secure data that is several layers deep in the hierarchy. Therefore it's important to understand how to best work with this hierarchical data.

All documents in Cloud Firestore are stored in a path that begins with /databases/database name/documents -- this path is generally not visible to clients working with Cloud Firestore. A client might be writing data to the employees/stanley document, but from the perspective of the Cloud Firestore Security Rules, this write is occurring at databases/database name/documents/employees/stanley.

Exact matches

Occasionally you might want to specify rules for a specific document in Cloud Firestore. To do so, you could write the full path to that document, as in the following example:

service cloud.firestore {
  // An exact match for the Serenity spaceship in our database
  match /databases/{database}/documents/spaceships/serenity {
    // Rules go here...
  }
  // An exact match for an employee named Stanley
  match /databases/{database}/documents/employees/stanley {
    // Rules go here...
  }
}

Nested matches

Writing out long paths to a particular document or collection can start to be tedious after a while, so you can create nested matches by adding match clauses inside of other ones.

service cloud.firestore {
  match /databases/{database}/documents/spaceships/serenity {
      // Rules written in this section would apply to the document located at
      // /databases/{database}/document/spaceships/serenity
    match /crew/jayne {
      // Rules written in this section would apply to the document located at
      // /databases/{database}/document/spaceships/serenity/crew/jayne
    }
  }
}

With nested matches, it is very common to see /databases/{database}/documents/ presented as an outer match, with project-specific security rules added inside.

service cloud.firestore {
  match /databases/{database}/documents {
    match /spaceships/serenity {
      // Rules go here for the "Serenity" spaceship
      match /crew/jayne {
        // Rules go here for the "Jayne" crew member
      }
    }
    match /employees/stanley {
      // Rules go here for employees named Stanley
    }
  }
}

Rules don't cascade

Security rules in Cloud Firestore don't cascade unless you explicity use the =** wildcard (see below). That is, if you specify security rules for documents within a collection, those rules will only apply to that document, and not to documents within any subcollections of that document.

In the following example, security rules would be applied to the /spaceships/serenity document, but they would not be applied to a /spaceships/serenity/crew/hoban document. (Therefore, by default, nobody would be able to access that crew member.)

service cloud.firestore {
  match /databases/{database}/documents {
    match /spaceships/serenity {
      // Rules go here for the Serenity spaceship.
      // Right now, there are NO rules allowing access to documents in the
      // crew subcollection.
    }
  }
}

Rules for collections don't apply to documents within that collection. It's unusual (and probably an error) to have a security rule that is written at the collection level instead of the document level.

In the following example, the developer may have intended to make the spaceships collection viewable by the public, but in practice, nobody will be able to read any document within the spaceships collection, because no rules have been written at the document level.

service cloud.firestore {
  match /databases/{database}/documents {
    // This is probably a mistake
    match /spaceships {
      allow read;
      // In spite of the above line, a user can't read any document within the 
      // spaceship collection.
    }
  }
}

Using wildcards

In most cases, you'll want to write rules that apply to any document within a collection, not just a specific one. In these situations, you can use wildcards to specify any element within a path. A wildcard is created by adding curly brackets around a string.

In the following example, the security rules written below would apply to any document in the spaceships collection.

service cloud.firestore {
  match /databases/{database}/documents {
    match /spaceships/{spaceship} {
      // Rules go here...
    }
  }
}

The value of the path component that matches the wildcard is assigned to the string inside the curly brackets. For example, in the above set of rules, if a user tried to write to the spaceships/enterprise document, the value of spaceship would be "enterprise", and you could access that value later in the security rules by referencing the spaceship variable.

Remember, however, that any rules written in the above example would only apply to documents within the spaceships collection. If you wanted those rules to apply to any subcollections, you would need recursive matching.

Recursive matching with wildcards

Adding =** to the end of a wildcard match will not only match path elements at that level, but it will also recurisvely match all documents or subcollections underneath. For example, in the following security rules, any rules added would apply to both the spaceships/serenity document and the spaceships/enterprise/crew/troi document.

service cloud.firestore {
  match /databases/{database}/documents {
    match /spaceships/{spaceship=**} {
      // Rules go here...
      // These rules would also apply to all documents in the "spaceship"
      // collection, as we add documents in the "crew" subcollection.
    }
  }
}

Note that in the previous example, the string that's assigned to the spaceship wildcard variable is not just the path element at that level, but the entire matching segement of the path.

For example, if a client were attempting to update the spaceships/enterprise/crew/troi document, the value of spaceship would be enterprise/crew/troi. If you wanted to capture just the name of the spaceship document in the wildcard, you would need to restructure your security rules to look more like this:

service cloud.firestore {
  match /databases/{database}/documents {
    match /spaceships/{spaceship} {
      // Rules added here would apply to documents in the "spaceships"
      // collection.
      // The value of "spaceship" as captured by the wildcard  would just be
      // the name of the spaceship document
      match /{allChildren=**} {
        // Rules added here would apply to documents in any subcollections
      }
    }
  }
}

The above example is one situation where it looks as though you're writing security rules that match a subcollection. But because of the =** wildcard, these rules would still apply to documents within that subcollection.

Multiple rules for the same document

You may end up in a situation where multiple rules match the same document. In these cases, an operation is allowed if any rule exists that would allow that operation.

For example, in the following set of rules, we have one rule that only allows signed in users to read in any data that's part of a spaceship subcollection, and a second rule that allows the general public to read any document that's in the publicData subcollection.

service cloud.firestore {
  match /databases/{database}/documents {
    match /spaceships/{spaceship} {
      // Rules would go here for the 'spaceship' documents
      match /{allChildren=**} {
         // Only signed in users can read this data
         allow read: if request.auth.uid != null;
      }
      match /publicData/{publicDocs} {
         // Make this publicly readable
         allow read;
      }
    }
  }
}

In this case, a non-signed in user could still access the spaceships/pegasus/publicData/manifest document, because there is at least one rule which allows this action.

Evaluating security rules

All rules in Cloud Firestore Security Rules take the form of a boolean expression which determines whether or not an action is allowed. The default behavior is false. If there are no rules explicitly allowing a behavior, that action will not be permitted.

Sometimes you will explicitly permit or exclude all actions simply by declaring them true or false in the rules:

service cloud.firestore {
  match /databases/{database}/documents {
    match /spaceships/{spaceship}/publicData/{public} {
      // All data is publicly readable; nobody can write to it
      allow read;
    }
    match /users/{user}/logs/{log} {
      // All data is writable; nobody can read it
      allow write;
      // Technically, this line isn't necessary, but it's a useful way
      // of making your intentions known.
      allow read: if false;
    }
    match /vault/{superSecret=**} {
      // Nobody can read or write anything in the vault
    }
  }
}

Conditional logic

While explicitly setting rules to true or false is occasionally useful, more often you will want to write rules that conditionally allow actions if they meet a certain criteria.

Cloud Firestore Security Rules contain a number of built-in functions that you can use to evaluate the data belonging to the request coming in, or the data that already exists in the database.

For a full list of functions available to you, please see the reference documentation, but here are a number of common methods you might find useful.

  • Use == and != for equality checking, both for numbers and for strings.
  • Use == null or != null to check whether a field or value exists within a document.
  • Use is to verify that a piece of data is of a certain type. Valid types to check are string, int, float, bool, null, timestamp, list, and map.
  • Use in to verify that a value is in a list or a map.
  • Use the get() function to convert a document into a map.
  • Use exists() to check whether or not a document exists in the database.

Working with maps

Much of the data you will be working with in Cloud Firestore Security Rules take the form of maps; a collection of key-value pairs. You can access fields within maps either by using dot notation, or by using square brackets. When using square brackets, the name of the field you're evaluating should be in quotes.

// These two rules are the same, and are requesting the 'displayName' field of
// the 'auth' token map that's part of the 'request' variable.
allow read: if request.auth.token.displayName == "Malcolm Reynolds";
allow read: if request["auth"]["token"]["displayName"] == "Malcolm Reynolds";

The request variable

The request variable is a map that contains information about the request that's coming in from the client. You can view the reference documentation for a full list of fields, but two fields you'll frequently be working with are the auth field, which contains information from Firebase Authentication about the currently signed-in user, and the resource field, which contains information about the document being submitted.

Common use cases for the request object include:

  • Confirming that request.auth is not null to ensure that a user is signed in
  • Using the value of request.auth.uid to get the user's ID, as verified by Firebase Authentication
  • Using request.resource.data.<key> to ensure the value being submitted for a document write meets a particular format. This is most common way to perform data validation within your database.
service cloud.firestore {
  match /databases/{database}/documents {
    match /bulletinBoard/{note} {
      // Anybody can read these messages, as long as they're signed in.
      allow read, write: if request.auth != null;
    }
    match /users/{userID}/myNotes/{note} {
      // Anybody can write to their own notes section
      allow read, write: if request.auth.uid == userID;
    }
    match /spaceships/{spaceship} {
      allow read;
      // Spaceship documents can only contain three fields -- a name, a catchy
      // slogan, and cargo capacity greater than 6500.
      // Only these three fields are allowed, and this will evaluate to false
      // if any of these fields are null.
      allow write: if request.resource.data.size() == 3 &&
                   request.resource.data.name is string &&
                   request.resource.data.slogan is string &&
                   request.resource.data.cargo is int &&
                   request.resource.data.cargo > 6500;
    }
  }
}

Note that in the second rules expression above, the userID variable is taken from the wildcard expression in the data path itself. For more about extracting variables from wildcards, see below.

The resource variable

The resource variable is similar to the request.resource variable, but it refers to the document that already exists in the database. In the case of a read operation, this is the document that is about to be read, while in a write operation, this is the old document that is about to be updated or altered.

Both the resource and request.resource objects are maps. Each contains a __name__ property indicating the document name, and a data property where all user-defined properties are declared. By accessing keys within the data property of these maps, you can get the values of the various fields belonging to the document being referenced. For example, resource.data.kesselRun is mapped to the value of the kesselRun field within the document represented by the resource variable.

Here are a few examples using the resource object.

service cloud.firestore {
  match /databases/{database}/documents {
    match /bulletinBoard/{note} {
      // You can read any document that has a custom field named "visibility"
      // set to the string "public" or "read-only"
      allow read: if resource.data.visibility in ["public", "read-only"];
    }
    match /products/{productID}/reviews/{review} {
      allow read;
      // A user can update a product reviews, but they can't change
      // the headline.
      // Also, they should only be able up update their own product review,
      // and they still have to list themselves as an author
      allow update: if request.resource.data.headline == resource.data.headline
                    && resource.data.authorID == request.auth.userID
                    && request.resource.data.authorID == request.auth.userID;
  }
}

Using variables from wildcards

When you match a rule using a database path with a wildcard, a variable is created with a name matching the string you included inside the curly brackets. You can then reference these variables in your security rules.

One common use of these types of wildcards is to store documents by userID, and then only allow users to access documents for which their userID matches the ID of the document.

service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{userIDFromWildcard}/ {
      // Users can only edit documents in the database if the documentID is
      // equal to their userID
      allow read, write: if request.auth.uid == userIDFromWildcard;
    }
  }
}

Evaluating documents currently in the database

Using the resource variable will let you evaluate the current document that is about to be accessed, but sometimes you will want to evaluate other documents in other parts of the database.

You can do so by calling the get() function with the document path in the database. This will retrieve the current document, which will allow you to access custom fields through the data property, just like the resource object.

One very common use of this function is to evaluate an access control list and determine if the userID of the current user is part of that list.

The exists() function can also be used to determine if a document already exists.

When entering a path into the exists or get function, you do not include quotes. If you want to include a variable in the path name, you should enclose the variable in parenthesis and preface it with a dollar sign.

service cloud.firestore {
  match /databases/{database}/documents {
    match /games/{game}/playerProfiles/{playerID} {
      // Every "game" document has the userID of a referee, who is allowed to
      // alter player profiles
      allow write: if get(/databases/$(database)/documents/games/$(game)).data.referee == request.auth.uid;

      // All players in a game are allowed to view the player profiles of any
      // other player.
      allow read: if exists(/databases/$(database)/documents/games/$(game)/playerProfiles/$(request.auth.uid));
    }
    match /guilds/{guildID}/bulletinBoard/{post} {
       // Assume our guild document includes a "users" field, which itself is
       // a map consisting of a player ID and their role. For example:
       // {"user_123": "Member", "user_456": "Probation", "user_789": "Admin"}
       //
       // A player can write to the bulletin board if they're listed in the
       // guild's "users" map field as a "Member" or "Admin"
       allow write: if get(/databases/$(database)/documents/guilds/$(guildID)).data.users[(request.auth.uid)] in ["Admin", "Member"];
    }
  }
}

Note that both the get() and exists() functions cannot be used on collections.

Writing custom functions

It's possible to write custom functions for Cloud Firestore Security Rules. Functions only consist of a single return statement.

You can use custom functions in place of having to write the same security rule multiple times, which can often happen when you're applying rules to a document and its subcollections.

Functions can be declared anywhere inside your rules. If they are declared from within a match block, they can access any wildcard variables defined in that block. All functions can also use the request and resource variables as well.

For example, instead of these rules...

service cloud.firestore {
  match /databases/{database}/documents {
    match /projects/{projectID} {
      // A user can update a project if they're listed in the project's
      // "members" array. Or if the project is a "all-access" project.
      allow update: if request.auth.uid in get(/databases/$(database)/documents/projects/$(projectID)).data.members ||
                     get(/databases/$(database)/documents/projects/$(projectID)).data.accessType == "all-access";
      match /{allChildren=**} {
        // The same thing holds true for subcollections of this project
        allow update: if request.auth.uid in get(/databases/$(database)/documents/projects/$(projectID)).data.members ||
                      get(/databases/$(database)/documents/projects/$(projectID)).data.accessType == "all-access";
      }
    }
  }
}

You can write this instead:

service cloud.firestore {
  match /databases/{database}/documents {
    match /projects/{projectID} {

      function canUserUpdateProject() {
        return request.auth.uid in get(/databases/$(database)/documents/project/$(projectID)).data.members ||
         get(/databases/$(database)/documents/projects/$(projectID)).data.accessType == "all-access";
      }

      allow update: if canUserUpdateProject();
      match /{allChildren=**} {
        allow update: if canUserUpdateProject();
      }
    }
  }
}

Or this:


service cloud.firestore {
  match /databases/{database}/documents {
    match /projects/{projectID} {

      function isProjectAllAccess() {
        return get(/databases/$(database)/documents/projects/$(projectID)).data.accessType == "all-access";
      }
      function isUserOfficialMember() {
        return request.auth.uid in get(/databases/$(database)/documents/project/$(projectID)).data.members;
      }
      function canUserUpdateProject() {
        return isUserOfficialMember() || isProjectAllAccess();
      }

      allow update: if canUserUpdateProject();
      match /{allChildren=**} {
        allow update: if canUserUpdateProject();
      }
    }
  }
}

Secure writes

There are three operations for writes in Cloud Firestore: create, update, and delete. These correspond to the set(), add(), update(), remove(), and transaction() methods in the client libraries. For your convenience the write operation allows all of these.

service cloud.firestore {
  match /databases/{database}/documents {
    // Writes are divided into create, update, and delete operations
    match /myCollection/myDocument {
      allow create, update, delete: if <condition>;
    }

    // This is equivalent to using the write operation
    match /myCollection/myDocument {
      allow write: if <condition>;
    }
  }
}

Write rules are primarily used to validate the contents of documents being written. For instance, to ensure that certain fields are present while others are absent, or to ensure that fields are of a certain type.

We need to ensure the document being written, represented in Cloud Firestore Security Rules by the request.resource property, fits our criteria.

service cloud.firestore {
  match /databases/{database}/documents {
    match /rooms/{roomId} {
      match /messages/{messageId} {
        // A message must only contain two string fields, named "name" and "text"
        allow write: if request.resource.data.keys().hasAll(["name", "text"])
                     && request.resource.data.size() == 2
                     && request.resource.data.name is string
                     && request.resource.data.text is string;
    }
  }
}

You can test these rules using the following code.

// Add new messages to our room "chat"
var messagesRef = db.collection("rooms").doc("chat").collection("messages");

// This will succeed, because it meets our criteria!
messagesRef.add({
  name: "River Tam",
  text: "My food is problematic."
});

// This will fail, because it isn't a valid message according to our rule
//   "text" is a floating point number
//   "timestamp" is an extra field
messagesRef.add({
  name: "River Tam",
  text: 3.141592765,
  timestamp: Date.now()
});

If the rule doesn't allow the operation, the write will fail with an error.

When updating data, you can compare the document being written (request.resource) with the existing document (resource).

service cloud.firestore {
  match /databases/{database}/documents {
    match /rooms/{roomId} {
      match /messages/{messageId} {
        // Existing and potential values must be equal
        allow update: if request.resource.data.name == resource.data.name;
    }
  }
}

We can check this with the following code.

// This will fail, because the rule doesn't allow this change
messagesRef.doc("some-uuid").update({
  name: "Jubal Early"
});

Secure reads

There are two operations for reads and queries in Cloud Firestore: get and list. These correspond to the get(), where().get(), methods in the client libraries. For your convenience the read operation allows both of these.

service cloud.firestore {
  match /databases/{database}/documents {
    // Reads are divided into get and list operations
    match /myCollection/myDocument {
      allow get, list: if <condition>;
    }

    // This is equivalent to using the read operation
    match /myCollection/myDocument {
      allow read: if <condition>;
    }
  }
}

Document reads

Also known as "field-based queries" because you are querying a collection for a document with a known field. Cloud Firestore Security Rules can protect an individual document from an unauthorized read. Let's say we want to restrict a single user to only reading their messages.

service cloud.firestore {
  match /databases/{database}/documents {
    match /rooms/{roomId} {
      match /messages/{messageId} {
        // Allow reads if the name is "Inara Serra"
        allow read: if resource.data.name == "Inara Serra";
    }
  }
}
// Read a particular message from our chat room
messagesRef.doc("some-message-owned-by-inara").get().then(function(document) {
  // Query succeeds, message should contain "name" and "text" properties
  var message = document.data();
});

Shallow queries

A shallow query fetches all the documents in a collection that match the given filters. An interesting property about Cloud Firestore Security Rules is that the same rule written above to prevent an read on an individual document can also apply to queries made on the collection containing that document.

When performing a rules check on a query, Cloud Firestore Security Rules will check to ensure that the user has access to all results before executing the query. If a query could return results a user doesn't have access to, the entire query fails and Firestore returns an error.

// Read all messages by Inara
messagesRef.where("name", "==", "Inara Serra").get().then(function(documentSet) {
  documentSet.forEach(function(document) {
    // Query succeeds, message should contain "name" and "text" properties
    var message = document.data();
  });
});

Send feedback about...

Need help? Visit our support page.