The behavior for system Date objects stored in Firestore is going to change AND YOUR APP MAY BREAK. To hide this warning and ensure your app does not break, you need to add the following code to your app before calling any other Cloud Firestore methods:
let db = Firestore.firestore()
let settings = db.settings
settings.areTimestampsInSnapshotsEnabled = true
db.settings = settings
With this change, timestamps stored in Cloud Firestore will be read back as Firebase Timestamp objects instead of as system Date objects. So you will also need to update code expecting a Date to instead expect a Timestamp. For example:
// old:
let date: Date = documentSnapshot.get("created_at") as! Date
// new:
let timestamp: Timestamp = documentSnapshot.get("created_at") as! Timestamp
let date: Date = timestamp.dateValue()
Please audit all existing usages of Date when you enable the new behavior. In a future release, the behavior will be changed to the new behavior, so if you do not follow these steps, YOUR APP MAY BREAK.
The behavior for system Date objects stored in Firestore is going to change AND YOUR APP MAY BREAK. To hide this warning and ensure your app does not break, you need to add the following code to your app before calling any other Cloud Firestore methods:
let db = Firestore.firestore()
let settings = db.settings
settings.areTimestampsInSnapshotsEnabled = true
db.settings = settings
With this change, timestamps stored in Cloud Firestore will be read back as Firebase Timestamp objects instead of as system Date objects. So you will also need to update code expecting a Date to instead expect a Timestamp. For example:
// old:
let date: Date = documentSnapshot.get("created_at") as! Date
// new:
let timestamp: Timestamp = documentSnapshot.get("created_at") as! Timestamp
let date: Date = timestamp.dateValue()
Please audit all existing usages of Date when you enable the new behavior. In a future release, the behavior will be changed to the new behavior, so if you do not follow these steps, YOUR APP MAY BREAK.
Cloud Firestore stores data in Documents, which are stored in Collections. Cloud Firestore creates collections and documents implicitly the first time you add data to the document. You do not need to explicitly create collections or documents.
Create a new collection and a document using the following example code.
// Add a new document with a generated ID var ref: DocumentReference? = nil ref = db.collection("users").addDocument(data: [ "first": "Ada", "last": "Lovelace", "born": 1815 ]) { err in if let err = err { print("Error adding document: (err)") } else { print("Document added with ID: (ref!.documentID)") } }
Now add another document to the users collection. Notice that this document includes a key-value pair (middle name) that does not appear in the first document. Documents in a collection can contain different sets of information.
// Add a second document with a generated ID. ref = db.collection("users").addDocument(data: [ "first": "Alan", "middle": "Mathison", "last": "Turing", "born": 1912 ]) { err in if let err = err { print("Error adding document: (err)") } else { print("Document added with ID: (ref!.documentID)") } }
Read data
To quickly verify that you’ve added data to Cloud Firestore, use the data viewer in the Firebase console.
You can also use the “get” method to retrieve the entire collection.
db.collection("users").getDocuments() { (querySnapshot, err) in if let err = err { print("Error getting documents: (err)") } else { for document in querySnapshot!.documents { print("(document.documentID) => (document.data())") } } }
Every document in Cloud Firestore is uniquely identified by its location within the database. The previous example showed a document alovelace within the collection users. To refer to this location in your code, you can create areference to it.
let alovelaceDocumentRef = db.collection("users").document("alovelace")
A reference is a lightweight object that just points to a location in your database. You can create a reference whether or not data exists there, and creating a reference does not perform any network operations.
You can also create references to collections:
let usersCollectionRef = db.collection("users")
For convenience, you can also create references by specifying the path to a document or collection as a string, with path components separated by a forward slash (/). For example, to create a reference to the alovelace document:
let aLovelaceDocumentReference = db.document("users/alovelace")
This page describes the data types that Cloud Firestore supports.
Value type ordering
When a query involves a field with values of mixed types, Cloud Firestore uses a deterministic ordering based on the internal representations. The following list shows the order:
Null values
Boolean values
Integer and floating-point values, sorted in numerical order
To create or overwrite a single document, use the set() method:
// Add a new document in collection "cities" db.collection("cities").document("LA").setData([ "name": "Los Angeles", "state": "CA", "country": "USA" ]) { err in if let err = err { print("Error writing document: (err)") } else { print("Document successfully written!") } }
If the document does not exist, it will be created. If the document does exist, its contents will be overwritten with the newly provided data, unless you specify that the data should be merged into the existing document, as follows:
// Update one field, creating the document if it does not exist. db.collection("cities").document("BJ").setData([ "capital": true ], merge: true)
If you’re not sure whether the document exists, pass the option to merge the new data with any existing document to avoid overwriting entire documents.
Data types
Cloud Firestore lets you write a variety of data types inside a document, including strings, booleans, numbers, dates, null, and nested arrays and objects. Cloud Firestore always stores numbers as doubles, regardless of what type of number you use in your code.
Custom objects
java로 만 가능하다.즉 android에서만 적용 가능
Add a document
When you use set() to create a document, you must specify an ID for the document to create. For example:
But sometimes there isn’t a meaningful ID for the document, and it’s more convenient to let Cloud Firestore auto-generate an ID for you. You can do this by calling add():
// Add a new document with a generated id. var ref: DocumentReference? = nil ref = db.collection("cities").addDocument(data: [ "name": "Tokyo", "country": "Japan" ]) { err in if let err = err { print("Error adding document: (err)") } else { print("Document added with ID: (ref!.documentID)") } }
In some cases, it can be useful to create a document reference with an auto-generated ID, then use the reference later. For this use case, you can call doc():
let newCityRef = db.collection("cities").document()
// later... newCityRef.setData([ // ... ])
Behind the scenes, .add(...) and .doc().set(...) are completely equivalent, so you can use whichever is more convenient.
Update a document
To update some fields of a document without overwriting the entire document, use the update() method:
let washingtonRef = db.collection("cities").document("DC")
// Set the "capital" field of the city 'DC' washingtonRef.updateData([ "capital": true ]) { err in if let err = err { print("Error updating document: (err)") } else { print("Document successfully updated") } }
Update fields in nested objects
If your document contains nested objects, you can use “dot notation” to reference nested fields within the document when you call update():
// Create an initial document to update. let frankDocRef = db.collection("users").document("frank") frankDocRef.setData([ "name": "Frank", "favorites": [ "food": "Pizza", "color": "Blue", "subject": "recess" ], "age": 12 ])
// To update age and favorite color: db.collection("users").document("frank").updateData([ "age": 13, "favorites.color": "Red" ]) { err in if let err = err { print("Error updating document: (err)") } else { print("Document successfully updated") } }
You can also add server timestamps to specific fields in your documents, to track when an update was received by the server:
db.collection("objects").document("some-id").updateData([ "lastUpdated": FieldValue.serverTimestamp(), ]) { err in if let err = err { print("Error updating document: (err)") } else { print("Document successfully updated") } }
Update elements in an array
If your document contains an array field, you can use arrayUnion() and arrayRemove() to add and remove elements. arrayUnion() adds elements to an array but only elements not already present. arrayRemove() removes all instances of each given element.
let washingtonRef = db.collection("cities").document("DC")
// Atomically add a new region to the "regions" array field. washingtonRef.updateData([ "regions": FieldValue.arrayUnion(["greater_virginia"]) ])
// Atomically remove a region from the "regions" array field. washingtonRef.updateData([ "regions": FieldValue.arrayRemove(["east_coast"]) ])
Transactions: a transaction is a set of read and write operations on one or more documents.
Batched Writes: a batched write is a set of write operations on one or more documents.
Updating data with transactions
Using the Cloud Firestore client libraries, you can group multiple operations into a single transaction. Transactions are useful when you want to update a field’s value based on its current value, or the value of some other field. You could increment a counter by creating a transaction that reads the current value of the counter, increments it, and writes the new value to Cloud Firestore.
A transaction consists of any number of get() operations followed by any number of write operations such as set(),update(), or delete(). In the case of a concurrent edit, Cloud Firestore runs the entire transaction again. For example, if a transaction reads documents and another client modifies any of those documents, Cloud Firestore retries the transaction. This feature ensures that the transaction runs on up-to-date and consistent data.
Transactions never partially apply writes. All writes execute at the end of a successful transaction.
When using transactions, note that:
Read operations must come before write operations.
A function calling a transaction (transaction function) might run more than once if a concurrent edit affects a document that the transaction reads.
Transaction functions should not directly modify application state.
Transactions will fail when the client is offline.
The following example shows how to create and run a transaction:
let sfReference = db.collection("cities").document("SF")
db.runTransaction({ (transaction, errorPointer) -> Any? in let sfDocument: DocumentSnapshot do { try sfDocument = transaction.getDocument(sfReference) } catch let fetchError as NSError { errorPointer?.pointee = fetchError return nil }
guard let oldPopulation = sfDocument.data()?["population"] as? Int else { let error = NSError( domain: "AppErrorDomain", code: -1, userInfo: [ NSLocalizedDescriptionKey: "Unable to retrieve population from snapshot (sfDocument)" ] ) errorPointer?.pointee = error return nil }
transaction.updateData(["population": oldPopulation + 1], forDocument: sfReference) return nil }) { (object, error) in if let error = error { print("Transaction failed: (error)") } else { print("Transaction successfully committed!") } }
Passing information out of transactions
Do not modify application state inside of your transaction functions. Doing so will introduce concurrency issues, because transaction functions can run multiple times and are not guaranteed to run on the UI thread. Instead, pass information you need out of your transaction functions. The following example builds on the previous example to show how to pass information out of a transaction:
let sfReference = db.collection("cities").document("SF")
db.runTransaction({ (transaction, errorPointer) -> Any? in let sfDocument: DocumentSnapshot do { try sfDocument = transaction.getDocument(sfReference) } catch let fetchError as NSError { errorPointer?.pointee = fetchError return nil }
guard let oldPopulation = sfDocument.data()?["population"] as? Int else { let error = NSError( domain: "AppErrorDomain", code: -1, userInfo: [ NSLocalizedDescriptionKey: "Unable to retrieve population from snapshot (sfDocument)" ] ) errorPointer?.pointee = error return nil }
transaction.updateData(["population": newPopulation], forDocument: sfReference) return newPopulation }) { (object, error) in if let error = error { print("Error updating population: (error)") } else { print("Population increased to (object!)") } }
Transaction failure
A transaction can fail for the following reasons:
The transaction contains read operations after write operations. Read operations must always come before any write operations.
The transaction read a document that was modified outside of the transaction. In this case, the transaction automatically runs again. The transaction is retried a finite number of times.
A failed transaction returns an error and does not write anything to the database. You do not need to roll back the transaction; Cloud Firestore does this automatically.
Batched writes
If you do not need to read any documents in your operation set, you can execute multiple write operations as a single batch that contains any combination of set(), update(), or delete() operations. A batch of writes completes atomically and can write to multiple documents.
Batched writes are also useful for migrating large data sets to Cloud Firestore. A batched write can contain up to 500 operations and batching operations together reduces connection overhead resulting in faster data migration.
Batched writes have fewer failure cases than transactions and use simpler code. They are not affected by contention issues, because they don’t depend on consistently reading any documents. Batched writes execute even when the user’s device is offline. The following example shows how to build and commit a batch of writes:
// Get new write batch let batch = db.batch()
// Set the value of 'NYC' let nycRef = db.collection("cities").document("NYC") batch.setData([:], forDocument: nycRef)
// Update the population of 'SF' let sfRef = db.collection("cities").document("SF") batch.updateData(["population": 1000000 ], forDocument: sfRef)
// Delete the city 'LA' let laRef = db.collection("cities").document("LA") batch.deleteDocument(laRef)
// Commit the batch batch.commit() { err in if let err = err { print("Error writing batch (err)") } else { print("Batch write succeeded.") } }
Data validation for atomic operations
For mobile/web client libraries, you can validate data using Cloud Firestore Security Rules. You can ensure that related documents are always updated atomically and always as part of a transaction or batched write. Use the getAfter()security rule function to access and validate the state of a document after a set of operations completes but beforeCloud Firestore commits the operations.
For example, imagine that the database for the cities example also contains a countries collection. Each countrydocument uses a last_updated field to keep track of the last time any city related to that country was updated. The following security rules require that an update to a city document must also atomically update the related country’s last_updated field:
service cloud.firestore { match /databases/{database}/documents { // If you update a city doc, you must also // update the related country's last_updated field. match /cities/{city} { allow write: if request.auth.uid != null && getAfter( /databases/$(database)/documents/countries/$(request.resource.data.country) ).data.last_updated == request.time; }
match /countries/{country} { allow write: if request.auth.uid != null; } } }
Security rules limits
In security rules for transactions or batched writes, there is a limit of 20 document access calls for the entire atomic operation in addition to the normal 10 call limit for each single document operation in the batch.
For example, consider the following rules for a chat application:
service cloud.firestore { match /databases/{db}/documents { function prefix() { return /databases/{db}/documents; } match /chatroom/{roomId} { allow read, write: if roomId in get(/$(prefix())/users/$(request.auth.uid)).data.chats || exists(/$(prefix())/admins/$(request.auth.uid)); } match /users/{userId} { allow read, write: if userId == request.auth.uid || exists(/$(prefix())/admins/$(request.auth.uid)); } match /admins/{userId} { allow read, write: if exists(/$(prefix())/admins/$(request.auth.uid)); } } }
The snippets below illustrate the number of document access calls used for a few data access patterns:
// 0 document access calls used, because the rules evaluation short-circuits // before the exists() call is invoked. db.collection('user').doc('myuid').get(...);
// 1 document access call used. The maximum total allowed for this call // is 10, because it is a single document request. db.collection('chatroom').doc('mygroup').get(...);
// Initializing a write batch... var batch = db.batch();
// 2 document access calls used, 10 allowed. var group1Ref = db.collection("chatroom").doc("group1"); batch.set(group1Ref, {msg: "Hello, from Admin!"});
// 1 document access call used, 10 allowed. var newUserRef = db.collection("users").doc("newuser"); batch.update(newUserRef, {"lastSignedIn": new Date()});
db.collection("cities").document("DC").delete() { err in if let err = err { print("Error removing document: (err)") } else { print("Document successfully removed!") } }
Warning: Deleting a document does not delete its subcollections!
각각 하부의 subcollection은 손수 삭제해야 한다.
Delete fields
To delete specific fields from a document, use the FieldValue.delete() method when you update a document:
db.collection("cities").document("BJ").updateData([ "capital": FieldValue.delete(), ]) { err in if let err = err { print("Error updating document: (err)") } else { print("Document successfully updated") } }
Delete collections
To delete an entire collection or subcollection in Cloud Firestore, retrieve all the documents within the collection or subcollection and delete them. If you have larger collections, you may want to delete the documents in smaller batches to avoid out-of-memory errors. Repeat the process until you’ve deleted the entire collection or subcollection.
Deleting a collection requires coordinating an unbounded number of individual delete requests. If you need to delete entire collections, do so only from a trusted server environment. While it is possible to delete a collection from a mobile/web client, doing so has negative security and performance implications.
The snippets below are somewhat simplified and do not deal with error handling, security, deleting subcollections, or maximizing performance. To learn more about one recommended approach to deleting collections in production, seeDeleting Collections and Subcollections.
Delete data with the Firebase CLI
You can also use the Firebase CLI to delete documents and collections. Use the following command to delete data:
There are two ways to retrieve data stored in Cloud Firestore.
Call a method to get the data.
Set a listener to receive data-change events.
Get a document
The following example shows how to retrieve the contents of a single document using get():
let docRef = db.collection("cities").document("SF")
docRef.getDocument { (document, error) in if let document = document, document.exists { let dataDescription = document.data().map(String.init(describing:)) ?? "nil" print("Document data: (dataDescription)") } else { print("Document does not exist") } }
Note: If there is no document at the location referenced by docRef, the resulting document will be empty and calling existson it will return false.
Source Options
For platforms with offline support, you can set the source option to control how a get call uses the offline cache.
By default, a get call will attempt to fetch the latest document snapshot from your database. On platforms with offline support, the client library will use the offline cache if the network is unavailable or if the request times out.
You can specify the source option in a get() call to change the default behavior. You can fetch from only the database and ignore the offline cache, or you can fetch from only the offline cache. For example:
let docRef = db.collection("cities").document("SF")
// Force the SDK to fetch the document from the cache. Could also specify // FirestoreSource.server or FirestoreSource.default. docRef.getDocument(source: .cache) { (document, error) in if let document = document { let dataDescription = document.data().map(String.init(describing:)) ?? "nil" print("Cached document data: (dataDescription)") } else { print("Document does not exist in cache") } }
Custom objects
The previous example retrieved the contents of the document as a map, but in some languages it’s often more convenient to use a custom object type. In Add Data, you defined a City class that you used to define each city. You can turn your document back into a City object:
let docRef = db.collection("cities").document("BJ")
docRef.getDocument { (document, error) in if let city = document.flatMap({ $0.data().flatMap({ (data) in return City(dictionary: data) }) }) { print("City: (city)") } else { print("Document does not exist") } }
Important: Each custom class must have a public constructor that takes no arguments. In addition, the class must include a public getter for each property.
Get multiple documents from a collection
You can also retrieve multiple documents with one request by querying documents in a collection. For example, you can use where() to query for all of the documents that meet a certain condition, then use get() to retrieve the results:
db.collection("cities").whereField("capital", isEqualTo: true) .getDocuments() { (querySnapshot, err) in if let err = err { print("Error getting documents: (err)") } else { for document in querySnapshot!.documents { print("(document.documentID) => (document.data())") } } }
By default, Cloud Firestore retrieves all documents that satisfy the query in ascending order by document ID, but you can order and limit the data returned.
Get all documents in a collection
In addition, you can retrieve all documents in a collection by omitting the where() filter entirely:
db.collection("cities").getDocuments() { (querySnapshot, err) in if let err = err { print("Error getting documents: (err)") } else { for document in querySnapshot!.documents { print("(document.documentID) => (document.data())") } } }
List subcollections of a document
The getCollections() method of the Cloud Firestore server client libraries lists all subcollections of a document reference.
Retrieving a list of collections is not possible with the mobile/web client libraries. You should only look up collection names as part of administrative tasks in trusted server environments. If you find that you need this capability in the mobile/web client libraries, consider restructuring your data so that subcollection names are predictable.
db.collection("cities").document("SF") .addSnapshotListener { documentSnapshot, error in guard let document = documentSnapshot else { print("Error fetching document: (error!)") return } guard let data = document.data() else { print("Document data was empty.") return } print("Current data: (data)") }
Events for local changes
Local writes in your app will invoke snapshot listeners immediately. This is because of an important feature called “latency compensation.” When you perform a write, your listeners will be notified with the new data before the data is sent to the backend.
Retrieved documents have a metadata.hasPendingWrites property that indicates whether the document has local changes that haven’t been written to the backend yet. You can use this property to determine the source of events received by your snapshot listener:
You can also chain multiple where() methods to create more specific queries (logical AND). However, to combine the equality operator (==) with a range or array-contains clause (<, <=, >, >=, or array_contains), make sure to create a composite index.
Cloud Firestore does not support the following types of queries:
Queries with range filters on different fields, as described in the previous section.
Single queries across multiple collections or subcollections. Each query runs against a single collection of documents. For more information about how your data structure affects your queries, see Choose a Data Structure.
Logical OR queries. In this case, you should create a separate query for each OR condition and merge the query results in your app.
Queries with a != clause. In this case, you should split the query into a greater-than query and a less-than query. For example, although the query clause where("age", "!=", "30") is not supported, you can get the same result set by combining two queries, one with the clause where("age", "<", "30") and one with the clause where("age", ">", 30).
You can combine where() filters with orderBy() and limit(). In the following example, the queries define a population threshold, sort by population in ascending order, and return only the first few results that exceed the threshold:
// Get all cities with population over one million, ordered by population. db.collection("cities") .order(by: "population") .start(at: [1000000])
// Get all cities with population less than one million, ordered by population. db.collection("cities") .order(by: "population") .end(at: [1000000])
Use a document snapshot to define the query cursor
document를 cursor로 직접 이용하는 경우 ( start()에 직접 document obj를 pass 한다 )
db.collection("cities") .document("SF") .addSnapshotListener { (document, error) in guard let document = document else { print("Error retreving cities: (error.debugDescription)") return }
// Get all cities with a population greater than or equal to San Francisco. let sfSizeOrBigger = db.collection("cities") .order(by: "population") .start(atDocument: document) }
Paginate a query
// Construct query for first 25 cities, ordered by population let first = db.collection("cities") .order(by: "population") .limit(to: 25)
first.addSnapshotListener { (snapshot, error) in guard let snapshot = snapshot else { print("Error retreving cities: (error.debugDescription)") return }
guard let lastSnapshot = snapshot.documents.last else { // The collection is empty. return }
// Construct a new query starting after this document, // retrieving the next 25 cities. let next = db.collection("cities") .order(by: "population") .start(afterDocument: lastSnapshot)
// Use the query for pagination. // ... }
Set multiple cursor conditions
조건에 해당하는 document가 여러개 인경우 조건을 좀더 명확하게 지정할수 있다.
// Will return all Springfields db.collection("cities") .order(by: "name") .order(by: "state") .start(at: ["Springfield"])
// Will return "Springfield, Missouri" and "Springfield, Wisconsin" db.collection("cities") .order(by: "name") .order(by: "state") .start(at: ["Springfield", "Missouri"])
Cloud Firestore uses two types of indexes, single-field and composite. Both types of indexes are uniquely defined by the fields they index and the index mode on each field.
Automatic indexing
By default, Cloud Firestore automatically maintains an index for each field in a document and each subfield in a map. Cloud Firestore uses the following settings for automatically created single-field indexes:
For each non-array and non-map field, Cloud Firestore defines two single-field indexes, one in ascending mode and one in descending mode.
For each map field, Cloud Firestore creates one ascending index and one descending index for each non-array and non-map subfield in the map.
For each array field in a document, Cloud Firestore creates and maintains an array-contains index.
If you need to run a compound query that uses a range comparison (<, <=, >, or >=) or if you need to sort by a different field, you must create a composite index for that query.
Composite indexes
A composite index stores a sorted mapping of all the documents in a collection that contain multiple specific fields instead of just one. A composite index also defines an index mode for each of its fields (ascending, descending, or array-contains), and the index is sorted based on the index modes.
Note: You can have at most one array field per composite index.
// Allow read/write access on all documents to any user signed in to the application service cloud.firestore { match /databases/{database}/documents { match /{document=**} { allow read, write: if request.auth.uid != null; } } }
CLI로 rule을 수정하는 경우 주의사항
Note: When you deploy security rules using the Firebase CLI, the rules defined in your project directory overwrite any existing rules in the Firebase console. So, if you choose to define or edit your security rules using the Firebase console, make sure that you also update the rules defined in your project directory.
Cloud Firestore Security Rules always begin with the following declaration:
service cloud.firestore { match /databases/{database}/documents { // ... } }
Basic read/write rules
service cloud.firestore { match /databases/{database}/documents {
// Match any document in the 'cities' collection match /cities/{city} { allow read: if <condition>; allow write: if <condition>; } } }
In the example above, the match statement uses the {city} wildcard syntax. This means the rule applies to any document in the cities collection, such as /cities/SF or /cities/NYC. When the allow expressions in the match statement are evaluated, the city variable will resolve to the city document name, such as SF or NYC.
Granular operations
service cloud.firestore { match /databases/{database}/documents { // A read rule can be divided into get and list rules match /cities/{city} { // Applies to single document read requests allow get: if <condition>;
// Applies to queries and collection read requests allow list: if <condition>; }
// A write rule can be divided into create, update, and delete rules match /cities/{city} { // Applies to writes to nonexistent documents allow create: if <condition>;
// Applies to writes to existing documents allow update: if <condition>;
// Applies to delete operations allow delete: if <condition>; } } }
Hierarchical data
service cloud.firestore { match /databases/{database}/documents { match /cities/{city} { allow read, write: if <condition>;
// Explicitly define rules for the 'landmarks' subcollection match /landmarks/{landmark} { allow read, write: if <condition>; } } } }
service cloud.firestore { match /databases/{database}/documents { match /cities/{city} { match /landmarks/{landmark} { allow read, write: if <condition>; } } } }
위와 아래는 같은 내용
service cloud.firestore { match /databases/{database}/documents { match /cities/{city}/landmarks/{landmark} { allow read, write: if <condition>; } } }
If you want rules to apply to an arbitrarily deep hierarchy, use the recursive wildcard syntax, {name=**}:
service cloud.firestore { match /databases/{database}/documents { // Matches any document in the cities collection as well as any document // in a subcollection. match /cities/{document=**} { allow read, write: if <condition>; } } }
When using the recursive wildcard syntax, the wildcard variable will contain the entire matching path segment, even if the document is located in a deeply nested subcollection. For example, the rules listed above would match a document located at /cities/SF/landmarks/coit_tower, and the value of the document variable would be SF/landmarks/coit_tower.
Recursive wildcards cannot match an empty path, so match /cities/{city}/{document=**} will match documents in subcollections but not in the cities collection, whereas match /cities/{document=**} will match both documents in the cities collection and subcollections.
It’s possible for a document to match more than one match statement. In the case where multiple allow expressions match a request, the access is allowed if any of the conditions is true:
service cloud.firestore { match /databases/{database}/documents { // Matches any document in the 'cities' collection. match /cities/{city} { allow read, write: if false; }
// Matches any document in the 'cities' collection or subcollections. match /cities/{document=**} { allow read, write: if true; } } }
In the example above, all reads and writes to the cities collection will be allowed because the second rule is always true, even though the first rule is always false.
One of the most common security rule patterns is controlling access based on the user’s authentication state. For example, your app may want to allow only signed-in users to write data:
service cloud.firestore { match /databases/{database}/documents { // Allow the user to access documents in the "cities" collection // only if they are authenticated. match /cities/{city} { allow read, write: if request.auth.uid != null; } } }
If your app uses Firebase Authentication, the request.auth variable contains the authentication information for the client requesting data. For more information about request.auth, see the reference documentation.
service cloud.firestore { match /databases/{database}/documents { // Make sure the uid of the requesting user matches name of the user // document. The wildcard expression {userId} makes the userId variable // available in rules. match /users/{userId} { allow read, update, delete: if request.auth.uid == userId; allow create: if request.auth.uid != null; } } }
Data validation
Many apps store access control information as fields on documents in the database. Cloud Firestore Security Rules can dynamically allow or deny access based on document data:
service cloud.firestore { match /databases/{database}/documents { // Allow the user to read data if the document has the 'visibility' // field set to 'public' match /cities/{city} { allow read: if resource.data.visibility == 'public'; } } }
The resource variable refers to the requested document, and resource.data is a map of all of the fields and values stored in the document. For more information on the resource variable, see the reference documentation.
When writing data, you may want to compare incoming data to existing data. In this case, if your ruleset allows the pending write, the request.resource variable contains the future state of the document. For update operations that only modify a subset of the document fields, the request.resource variable will contain the pending document state after the operation. You can check the field values in request.resource to prevent unwanted or inconsistent data updates:
service cloud.firestore { match /databases/{database}/documents { // Make sure all cities have a positive population and // the name is not changed match /cities/{city} { allow update: if request.resource.data.population > 0 && request.resource.data.name == resource.data.name; } } }
Access other documents
Using the get() and exists() functions, your security rules can evaluate incoming requests against other documents in the database. The get() and exists() functions both expect fully specified document paths. When using variables to construct paths for get() and exists(), you need to explicitly escape variables using the $(variable) syntax.
In the example below, the database variable is captured by the match statement match /databases/{database}/documents and used to form the path:
service cloud.firestore { match /databases/{database}/documents { match /cities/{city} { // Make sure a 'users' document exists for the requesting user before // allowing any writes to the 'cities' collection allow create: if exists(/databases/$(database)/documents/users/$(request.auth.uid))
// Allow the user to delete cities if their user document has the // 'admin' field set to 'true' allow delete: if get(/databases/$(database)/documents/users/$(request.auth.uid)).data.admin == true } } }
For writes, you can use the getAfter() function to access the state of a document after a transaction or batch of writes completes but before the transaction or batch commits. Like get(), the getAfter() function takes a fully specified document path. You can use getAfter() to define sets of writes that must take place together as a transaction or batch.
Custom functions
Functions can contain only a single return statement. They cannot contain any additional logic. For example, they cannot create intermediate variables, execute loops, or call external services.
Functions can automatically access functions and variables from the scope in which they are defined. For example, a function defined within the service cloud.firestore scope has access to the resource variable and built-in functions such as get() and exists().
Functions may call other functions but may not recurse. The total call stack depth is limited to 10.
A function is defined with the function keyword and takes zero or more arguments. For example, you may want to combine the two types of conditions used in the examples above into a single function:
service cloud.firestore { match /databases/{database}/documents { // True if the user is signed in or the requested data is 'public' function signedInOrPublic() { return request.auth.uid != null || resource.data.visibility == 'public'; }
match /cities/{city} { allow read, write: if signedInOrPublic(); }
match /users/{user} { allow read, write: if signedInOrPublic(); } } }
When writing queries to retrieve documents, keep in mind that security rules are not filters—queries are all or nothing. To save you time and resources, Cloud Firestore evaluates a query against its potential result set instead of the actual field values for all of your documents. If a query could potentially return documents that the client does not have permission to read, the entire request fails.
Note: This behavior applies to queries that retrieve one or more documents from a collection and not to individual document retrievals. When you use a document ID to retrieve a single document, Cloud Firestore reads the document and evaluates the request using your security rules and the actual document properties.
As the examples below demonstrate, you must write your queries to fit the constraints of your security rules.
Secure and query documents based on
auth.uid
The following example demonstrates how to write a query to retrieve documents protected by a security rule. Consider a database that contains a collection of story documents:
/stories/{storyid}
{ title: "A Great Story", content: "Once upon a time...", author: "some_auth_id", published: false }
In addition to the title and content fields, each document stores the author and published fields to use for access control. These examples assume the app uses Firebase Authentication to set the author field to the UID of the user who created the document. Firebase Authentication also populates the request.auth variable in the security rules.
The following security rule uses the request.auth and resource.data variables to restrict read and write access for each story to its author:
service cloud.firestore { match /databases/{database}/documents { match /stories/{storyid} { // Only the authenticated user who authored the document can read or write allow read, write: if request.auth.uid == resource.data.author; } } }
Suppose that your app includes a page that shows the user a list of story documents that they authored. You might expect that you could use the following query to populate this page. However, this query will fail, because it does not include the same constraints as your security rules:
Invalid: Query constraints do not match security rules constraints
// This query will fail db.collection("stories").get()
The query fails even if the current user actually is the author of every story document. The reason for this behavior is that when Cloud Firestore applies your security rules, it evaluates the query against its potential result set, not against the actual properties of documents in your database. If a query could potentially include documents that violate your security rules, the query will fail.
In contrast, the following query succeeds, because it includes the same constraint on the author field as the security rules:
Valid: Query constraints match security rules constraints
To further demonstrate the interaction between queries and rules, the security rules below expand read access for the stories collection to allow any user to read story documents where the published field is set to true.
service cloud.firestore { match /databases/{database}/documents { match /stories/{storyid} { // Anyone can read a published story; only story authors can read unpublished stories allow read: if resource.data.published == true || request.auth.uid == resource.data.author; // Only story authors can write allow write: if request.auth.uid == resource.data.author; } } }
The query for published pages must include the same constraints as the security rules:
The query constraint .where("published", "==", true) guarantees that resource.data.published is true for any result. Therefore, this query satisfies the security rules and is allowed to read data.
Evaluating constraints on queries
Your security rules can also accept or deny queries based on their constraints. The request.query variable contains the limit, offset, and orderBy properties of a query. For example, your security rules can deny any query that doesn’t limit the maximum number of documents retrieved to a certain range:
apply to requests for single documents, and rules for
list
apply to queries and requests for collections.
The following ruleset demonstrates how to write security rules that evaluate constraints placed on queries. This example expands the previous stories ruleset with the following changes:
The ruleset separates the read rule into rules for get and list.
The get rule restricts retrieval of single documents to public documents or documents the user authored.
The list rule applies the same restrictions as get but for queries. It also checks the query limit, then denies any query without a lmit or with a limit greater than 10.
The ruleset defines an authorOrPublished() function to avoid code duplication.
service cloud.firestore {
match /databases/{database}/documents {
match /stories/{storyid} {
// Returns `true` if the requested story is 'published' // or the user authored the story function authorOrPublished() { return resource.data.published == true || request.auth.uid == resource.data.author; }
// Deny any query not limited to 10 or fewer documents // Anyone can query published stories // Authors can query their unpublished stories allow list: if request.query.limit <= 10 && authorOrPublished();
// Anyone can retrieve a published story // Only a story's author can retrieve an unpublished story allow get: if authorOrPublished();
// Only a story's authors can write to a story allow write: if request.auth.uid == resource.data.author; }
Note: Offline persistence is supported only in Android, iOS, and web apps.
For Android and iOS, offline persistence is enabled by default. To disable persistence, set the PersistenceEnabled option to false.
For the web, offline persistence is disabled by default.
let settings = FirestoreSettings() settings.isPersistenceEnabled = true
// Any additional options // ...
// Enable offline data persistence let db = Firestore.firestore() db.settings = settings
Listen to offline data
While the device is offline, if you have enabled offline persistence, your listeners will receive listen events when the locally cached data changes. You can listen to documents, collections, and queries.
To check whether you’re receiving data from the server or the cache, use the fromCache property on the SnapshotMetadata in your snapshot event. If fromCache is true, the data came from the cache and might be stale or incomplete. If fromCache is false, the data is complete and current with the latest updates on the server.
By default, no event is raised if only the SnapshotMetadata changed. If you rely on the fromCache values, specify the includeMetadataChanges listen option when you attach your listen handler.
// Listen to metadata updates to receive a server snapshot even if // the data is the same as the cached data. db.collection("cities").whereField("state", isEqualTo: "CA") .addSnapshotListener(includeMetadataChanges: true) { querySnapshot, error in guard let snapshot = querySnapshot else { print("Error retreiving snapshot: (error!)") return }
for diff in snapshot.documentChanges { if diff.type == .added { print("New city: (diff.document.data())") } }
let source = snapshot.metadata.isFromCache ? "local cache" : "server" print("Metadata: Data fetched from (source)") }
Get offline data
If you get a document while the device is offline, Cloud Firestore returns data from the cache. If the cache does not contain data for that document, or the document does not exist, the get call returns an error.
Query offline data
Querying works with offline persistence. You can retrieve the results of queries with either a direct get or by listening, as described in the preceding sections. You can also create new queries on locally persisted data while the device is offline, but the queries will initially run only against the cached documents.
Disable and enable network access
You can use method below to disable network access for your Cloud Firestore client. While network access is disabled, all snapshot listeners and document requests retrieve results from the cache. Write operations are queued until network access is re-enabled.
Firestore.firestore().disableNetwork { (error) in // Do offline things // ... }
Use the following method to re-enable network access:
Firestore.firestore().enableNetwork { (error) in // Do online things // ... }