SKILL DETAIL
firebase-firestore
firebase/agent-skills/firebase-firestore
This skill is used to set up, manage, query, and configure Cloud Firestore databases (Standard/Enterprise edition). It covers data modeling, security rules, indexes, and SDK integrations for Web, Python, iOS, Android, and Flutter. Use it when creating or listing Firestore databases, defining data models or indexes, writing SDK queries, or integrating Firestore SDKs. The skill works best with the Firebase CLI but does not require it. First, identify the Firestore instance edition (Standard or Enterprise) by running `npx -y firebase-tools@latest firestore:databases:list`. Then, follow the appropriate guides based on the edition. If no database exists, default to creating an Enterprise edition database.
Installation
npx skills add https://github.com/firebase/agent-skills --skill firebase-firestore
Fichiers du skill
SKILL.md
Dernière synchronisation · 29 août 2026
references/enterprise/android_sdk_usage.md›
# Android SDK Usage (Enterprise Native Mode)
This guide covers the Firestore Android SDK (Kotlin) setup and usage patterns
optimized for Firestore Enterprise edition in Native mode.
______________________________________________________________________
## 1. Initialization
### Add Dependencies
In your module-level `build.gradle.kts` (usually `app/build.gradle.kts`), add
the Firebase Kotlin Bill of Materials (BoM) and the dependency for Cloud
Firestore:
> [!IMPORTANT] **[AGENT] RESOLVING THE RESOLVED SDK VERSION DETERMINISTICALLY**
> Never guess or hardcode a memorized out-of-date SDK version. Before adding
> dependencies, you MUST query the latest available versions directly from the
> Google Maven Repository:
>
> ```bash
> # Find the latest Firebase BoM version
> curl -s https://dl.google.com/dl/android/maven2/com/google/firebase/firebase-bom/maven-metadata.xml | grep -oE '<latest>[^<]+' | cut -d'>' -f2
>
> # Find the latest firebase-firestore version
> curl -s https://dl.google.com/dl/android/maven2/com/google/firebase/firebase-firestore/maven-metadata.xml | grep -oE '<latest>[^<]+' | cut -d'>' -f2
> ```
>
> If the dependency is already declared, fetch the exact resolved version
> currently active in the workspace's build graph:
>
> ```bash
> ./gradlew -q :app:dependencyInsight --dependency firebase-firestore --configuration releaseRuntimeClasspath
> ```
```kotlin
dependencies {
// [AGENT] Fetch the latest resolved version using the Gradle command above
implementation(platform("com.google.firebase:firebase-bom:<latest_bom_version>"))
// Add the dependency for the Cloud Firestore library
// When using the BoM, you don't specify versions in Firebase library dependencies
implementation("com.google.firebase:firebase-firestore")
}
```
### Initialize Firestore
Initialize the `FirebaseFirestore` instance in your ComponentActivity (Jetpack
Compose) or standard Activity/Fragment:
```kotlin
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import com.google.firebase.Firebase
import com.google.firebase.firestore.FirebaseFirestore
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Initialize the Firestore instance
val db = FirebaseFirestore.getInstance("<database-name>")
setContent {
MaterialTheme {
Text("Firestore initialized!")
}
}
}
}
```
______________________________________________________________________
## 2. Decision Framework: Mandatory Pipeline Architecture
> [!CRITICAL] **MANDATORY PIPELINE ARCHITECTURE (ZERO TOLERANCE)** You are
> strictly forbidden from using standard collection queries (e.g.,
> `db.collection(...).where(...)` or client-side mapping loops) as the primary
> implementation for relational joins, aggregations, or search algorithms.
>
> Server-side execution via pipelines is the superior, non-negotiable core
> architecture for Enterprise instances.
### Strict Rules & Accountability
1. **Mandatory Execution Engine**: Every repository lookup requiring related
data mapping, textual relevance scoring, or multi-document counting MUST be
authored natively using the server-side `db.pipeline()` API.
1. **Required Proof of Use**: In your task execution summaries, you MUST
explicitly highlight the exact pipeline stages (`.define()`, `.addFields()`,
`.search()`, `.aggregate()`) integrated into your primary data layer return
paths. Failure to implement pipelines natively constitutes an immediate
violation of instructions.
1. **Check API References**:
- **Locate**: Locate the standard Maven sources JAR (`-sources.jar`)
corresponding to the resolved `firebase-firestore` dependency version
inside your local Gradle or Maven dependency cache.
- **Extract Once**: Extract the documentation files `pipeline.docs.txt` and
`expressions.docs.txt` from the root directory of that `-sources.jar`
archive into a temporary workspace scratch directory of your choice.
- **Read & Reference**:
- **Read** the extracted `pipeline.docs.txt` once fully to understand core
pipeline structure and stage capabilities.
- **Reference** the extracted `expressions.docs.txt` on-demand for specific
function overloads and parameters.
______________________________________________________________________
## 3. Pipeline Examples
### Relational Joins Pattern
When querying related data (e.g., articles and their author profiles), perform
the join at the database level via pipeline stages instead of executing multiple
sequential lookups on the client-side.
- Use `.define()` to bind parameters or document properties as variables.
- Use `.addFields()` and a nested subquery with a matching filter.
- Use `.toScalarExpression()` to convert a nested pipeline subquery to a single
field value.
- Assign variable and field aliases using `.alias(...)` (note: while the Web SDK
uses `.as()`, the Kotlin SDK uses `.alias()` to avoid keyword conflicts with
Kotlin's `as` operator).
```kotlin
import com.google.firebase.firestore.pipeline.Expression.field
import com.google.firebase.firestore.pipeline.Expression.variable
// Fetch articles and join the associated author Profile side-by-side
val articlesWithAuthProfile = db.pipeline().collection("articles")
.define(field("authorUid").alias("author_id"))
.addFields(
db.pipeline().collection("users")
.where(field("__name__").documentId().equalTo(variable("author_id")))
.select(field("displayName"), field("avatarUrl"), field("handle"))
.toScalarExpression()
.alias("author")
)
```
### Full-Text Search
Leverage the database-native `.search()` stage within your pipelines to run
high-performance text query matches on the database level.
```kotlin
import com.google.firebase.firestore.pipeline.Expression.documentMatches
import com.google.firebase.firestore.pipeline.Expression.score
// Execute full-text search inside a pipeline, sorted by relevance score descending
val searchPipeline = db.pipeline()
.collection("articles")
.search(
query = documentMatches("machine learning"),
sort = score().descending()
)
.limit(5)
```
______________________________________________________________________
## 4. Real-Time Listener & Document Operations
When real-time data sync or transaction-based document mutations are strictly
required by application specifications, write clean operations as shown in this
comprehensive example.
```kotlin
import android.util.Log
import com.google.firebase.Firebase
import com.google.firebase.firestore.DocumentChange
import com.google.firebase.firestore.firestore
val db = Firebase.firestore
// 1. Add a new document to a collection
val taskData = hashMapOf(
"title" to "Refactor Android SDK Usage Guide",
"status" to "pending"
)
db.collection("tasks")
.add(taskData)
.addOnSuccessListener { documentReference ->
val taskId = documentReference.id
Log.d("Firestore", "Document added with ID: $taskId")
// 2. Update specific fields of an existing document without replacing it
db.collection("tasks").document(taskId)
.update("priority", "high")
.addOnSuccessListener {
Log.d("Firestore", "Document successfully updated!")
}
.addOnFailureListener { e ->
Log.w("Firestore", "Error updating document", e)
}
}
.addOnFailureListener { e ->
Log.w("Firestore", "Error adding document", e)
}
// 3. Establish a real-time listener on a collection query
db.collection("tasks")
.whereEqualTo("status", "pending")
.addSnapshotListener { snapshot, error ->
if (error != null) {
Log.w("Firestore", "Listen failed.", error)
return@addSnapshotListener
}
snapshot?.documentChanges?.forEach { change ->
val docId = change.document.id
val docData = change.document.data
when (change.type) {
DocumentChange.Type.ADDED -> {
Log.d("Firestore", "Added Task: $docId => $docData")
}
DocumentChange.Type.MODIFIED -> {
Log.d("Firestore", "Updated Task: $docId => $docData")
}
DocumentChange.Type.REMOVED -> {
Log.d("Firestore", "Removed Task: $docId => $docData")
}
}
}
}
```
references/enterprise/data_model.md›
# Firestore Data Model Reference
Firestore is a NoSQL, document-oriented database. Unlike a SQL database, there
are no tables or rows. Instead, you store data in **documents**, which are
organized into **collections**.
## Document Data Model
Data in Firestore is organized into documents, collections, and subcollections.
### Documents
A **document** is a lightweight record that contains fields, which map to
values. Each document is identified by a name. A document can contain complex
nested objects in addition to basic data types like strings, numbers, and
booleans. Documents are limited to a maximum size of 1 MiB.
Example document (e.g., in a `users` collection):
`json { "first": "Ada", "last": "Lovelace", "born": 1815 }`
### Collections
Documents live in **collections**, which are containers for your documents. For
example, you could have a `users` collection to contain your various users, each
represented by a document. * Collections can only contain documents. They cannot
directly contain raw fields with values, and they cannot contain other
collections. * Documents within a collection can contain different fields. * You
don't need to "create" or "delete" collections explicitly. After you create the
first document in a collection, the collection exists. If you delete all of the
documents in a collection, the collection no longer exists.
### Subcollections
Documents can contain subcollections natively. A subcollection is a collection
associated with a specific document. For example, a user document in the `users`
collection could have a `messages` subcollection containing message documents
exclusively for that user. This creates a powerful hierarchical data structure.
Data path example: `users/user1/messages/message1`
## Collection Group Support
A **collection group** consists of all collections with the same ID. By default,
queries retrieve results from a single collection in your database. Use a
collection group query to retrieve documents from a collection group instead of
from a single collection.
### Use Cases
Collection group queries are useful when you want to query across multiple
subcollections that share the same organizational structure.
For example, imagine an app with a `landmarks` collection where each landmark
has a `reviews` subcollection. If you want to find all 5-star reviews across
*all* landmarks, it would involve checking many separate `reviews`
subcollections. With a collection group, you can perform a single query against
the `reviews` collection group.
### Examples
**Standard Query** (Single Collection): Find all 5-star reviews for a specific
landmark.
`javascript db.collection('landmarks/golden_gate_bridge/reviews').where('rating', '==', 5)`
**Collection Group Query**: Find all 5-star reviews across *all* landmarks.
`javascript db.collectionGroup('reviews').where('rating', '==', 5)`
references/enterprise/flutter_setup.md›
# Cloud Firestore in Flutter
This guide covers basic CRUD operations, type-safe data modeling, and real-time
streams when using Cloud Firestore in a Flutter application via the
`cloud_firestore` package.
## 1. Setup
Ensure you have added the required dependency:
```bash
flutter pub add cloud_firestore
```
Also, ensure FlutterFire is configured properly for your target platforms.
______________________________________________________________________
## 2. Best Practices: Type-Safe Models
Instead of passing raw `Map<String, dynamic>` maps throughout your UI layer,
define a domain model class with `fromFirestore` and `toFirestore` converters to
maintain type safety.
```dart
import 'package:cloud_firestore/cloud_firestore.dart';
class Item {
final String id;
final String name;
final String ownerId;
final DateTime createdAt;
Item({
required this.id,
required this.name,
required this.ownerId,
required this.createdAt,
});
factory Item.fromFirestore(DocumentSnapshot doc) {
final data = doc.data() as Map<String, dynamic>? ?? {};
return Item(
id: doc.id,
name: data['name'] as String? ?? '',
ownerId: data['ownerId'] as String? ?? '',
createdAt: data['createdAt'] is Timestamp
? (data['createdAt'] as Timestamp).toDate()
: DateTime.now(),
);
}
Map<String, dynamic> toFirestore() {
return {
'name': name,
'ownerId': ownerId,
'createdAt': Timestamp.fromDate(createdAt),
};
}
}
```
______________________________________________________________________
## 3. The Service Layer
Encapsulate all database interactions within a dedicated service class to keep
your UI code clean and testable.
### Initialization & References
```dart
class ItemService {
// For Enterprise Native Mode, you often need to specify a non-default database ID:
final FirebaseFirestore _db = FirebaseFirestore.instanceFor(
app: Firebase.app(),
databaseId: 'my-database-id',
);
// Define your collection reference
CollectionReference get _itemsRef => _db.collection('items');
// 1. Create Data
Future<void> createItem(Item item) async {
try {
await _itemsRef.add(item.toFirestore());
} catch (e) {
print("Error creating document: $e");
}
}
// 2. Read Data (One-Time Fetch)
Future<List<Item>> fetchItems(String ownerId) async {
try {
final querySnapshot = await _itemsRef
.where('ownerId', isEqualTo: ownerId)
.orderBy('createdAt', descending: true)
.get();
return querySnapshot.docs.map((doc) => Item.fromFirestore(doc)).toList();
} catch (e) {
print("Error fetching documents: $e");
return [];
}
}
// 3. Read Data (Real-Time Stream)
Stream<List<Item>> streamItems(String ownerId) {
return _itemsRef
.where('ownerId', isEqualTo: ownerId)
.snapshots()
.map((snapshot) {
// If a custom composite index is missing during prototyping, apply sorting client-side:
final items = snapshot.docs.map((doc) => Item.fromFirestore(doc)).toList();
items.sort((a, b) => b.createdAt.compareTo(a.createdAt));
return items;
});
}
// 4. Update Data
Future<void> updateItemName(String id, String newName) async {
try {
await _itemsRef.doc(id).update({'name': newName});
} catch (e) {
print("Error updating document: $e");
}
}
// 5. Delete Data
Future<void> deleteItem(String id) async {
try {
await _itemsRef.doc(id).delete();
} catch (e) {
print("Error deleting document: $e");
}
}
}
```
______________________________________________________________________
## 4. Listening to Streams in the UI (`StreamBuilder`)
Use Flutter's `StreamBuilder` to rebuild the interface reactively whenever data
changes in your database collection.
```dart
StreamBuilder<List<Item>>(
stream: itemService.streamItems(currentUser.uid),
builder: (context, snapshot) {
if (snapshot.hasError) {
return const Center(child: Text('Failed to load data'));
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
final items = snapshot.data ?? [];
if (items.isEmpty) {
return const Center(child: Text('No items found.'));
}
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
return ListTile(
title: Text(item.name),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () => itemService.deleteItem(item.id),
),
);
},
);
},
);
```
references/enterprise/indexes.md›
# Firestore Indexes Reference
Indexes helps to improve query performance. Firestore Enterprise edition does
not create any indexes by default. By default, Firestore Enterprise performs a
full collection scan to find documents that match a query, which can be slow and
expensive for large collections. To avoid this, you can create indexes to
optimize your queries.
## Index Structure
An index consists of the following:
- a collection ID.
- a list of fields in the given collection.
- an order, either ascending or descending, for each field.
### Index Ordering
The order and sort direction of each field uniquely defines the index. For
example, the following indexes are two distinct indexes and not interchangeable:
- Field name `name` (ascending) and `population` (descending)
- Field name `name` (descending) and `population` (ascending)
### Index Density
Dense indexes: By default, Firestore indexes store data from all documents in a
collection. An index entry will be added for a document regardless of whether
the document contains any of the fields specified in the index. Non-existent
fields are treated as having a NULL value when generating index entries.
Sparse indexes: To change this behavior, you can define the index as a sparse
index. A sparse index indexes only the documents in the collection that contain
a value (including null) for at least one of the indexed fields. A sparse index
reduces storage costs and can improve performance.
### Unique Indexes
You can use unique index option to enforce unique values for the indexed fields.
For indexes on multiple fields, each combination of values must be unique across
the index. The database rejects any update and insert operations that attempt to
create index entries with duplicate values.
## Query Support Examples
| Query Type | Index Required |
| :--------------------------------------------------------- | :----------------------------------- |
| **Simple Equality**<br>\`where("a", | Single-Field Index on field `a` |
| : "==", 1)\` : : | |
| **Simple Range/Sort**<br>\`where("a", | Single-Field Index on field `a` |
| : ">", 1).orderBy("a")\` : : | |
| **Multiple Equality**<br>\`where("a", | Single-Field Index on field `a` and |
| : "==", 1).where("b", "==", 2)` :`b\` : | |
| \*\*Equality + | **Composite Index** on field `a` and |
| : Range/Sort\*\*<br>`where("a", "==", : `b\` : | |
| : 1).where("b", ">", 2)\` : : | |
| **Multiple Ranges**<br>\`where("a", | **Composite Index** on field `a` and |
| : ">", 1).where("b", ">", 2)` :`b\` : | |
| \*\*Array Contains + | **Composite Index** on field `tags` |
| : Equality\*\*<br>`where("tags", : and `active\` : | |
| : "array-contains", : : | |
| : "news").where("active", "==", true)\` : : | |
If no indexes is present, Firestore Enterprise will perform a full collection
scan to find documents that match a query.
## Management
### Config files
Your indexes should be defined in `firestore.indexes.json` (pointed to by
`firebase.json`).
Define a dense index:
```json
{
"indexes": [
{
"collectionGroup": "cities",
"queryScope": "COLLECTION",
"density": "DENSE",
"fields": [
{ "fieldPath": "country", "order": "ASCENDING" },
{ "fieldPath": "population", "order": "DESCENDING" }
]
}
],
"fieldOverrides": []
}
```
Define a sparse-any index:
```json
{
"indexes": [
{
"collectionGroup": "cities",
"queryScope": "COLLECTION",
"density": "SPARSE_ANY",
"fields": [
{ "fieldPath": "country", "order": "ASCENDING" },
{ "fieldPath": "population", "order": "DESCENDING" }
]
}
],
"fieldOverrides": []
}
```
Define a unique index:
```json
{
"indexes": [
{
"collectionGroup": "cities",
"queryScope": "COLLECTION",
"density": "SPARSE_ANY",
"unique": true,
"fields": [
{ "fieldPath": "country", "order": "ASCENDING" },
{ "fieldPath": "population", "order": "DESCENDING" }
]
}
],
"fieldOverrides": []
}
```
### CLI Commands
Deploy indexes only:
`bash npx firebase-tools@latest -y deploy --only firestore:indexes`
references/enterprise/ios_setup.md›
# Firestore Enterprise Native Mode on iOS
This guide walks you through using the Cloud Firestore SDK in your iOS app using
Swift.
# ⛔️ CRITICAL RULE: NO FirebaseFirestoreSwift ⛔️
UNDER NO CIRCUMSTANCES should you import, link against, or configure a project
to use `FirebaseFirestoreSwift`.
As of Firebase SDK v11+, all Swift-specific features (including `@DocumentID`,
`@ServerTimestamp`, and `Codable` support) have been fully merged into the main
`FirebaseFirestore` module.
- NEVER add `.external(name: "FirebaseFirestoreSwift")` or similar to SPM or
Xcode configurations.
- NEVER write `import FirebaseFirestoreSwift` in any Swift file.
- ONLY use `import FirebaseFirestore`.
This is a zero-tolerance constraint. Using `FirebaseFirestoreSwift` is
fundamentally incorrect and unacceptable.
# ⛔️ CRITICAL RULE: NO INLINE INITIALIZATION ⛔️
NEVER write `let db = Firestore.firestore()` or `Firestore.firestore(database:)`
as an inline class or struct property if there is ANY chance the object is
instantiated before `FirebaseApp.configure()` executes in the app root.
- **FATAL CRASH:**
`@Observable class DataManager { let db = Firestore.firestore() }` initialized
as a `@State` in the App root.
- **SAFE PATTERN:** Initialize `Firestore.firestore()` lazily
(`lazy var db = Firestore.firestore()`) OR explicitly initialize the manager
*after* `FirebaseApp.configure()` finishes.
## 1. Import and Initialize
Ensure you have installed the `FirebaseFirestore` SDK. Use the
`xcode-project-setup` skill to automate adding the SPM dependency to the Xcode
project.
```swift
import FirebaseFirestore
```
Initialize an instance of Cloud Firestore. **CRITICAL**: Enterprise databases
require a custom database ID and cannot use the `(default)` instance.
```swift
// Replace "your-enterprise-database-id" with your actual database ID
let db = Firestore.firestore(database: "your-enterprise-database-id")
```
## 2. Type-Safe Data Models (Codable)
To leverage modern Swift data modeling, define your data as `Codable` structs.
The main `FirebaseFirestore` module automatically supports mapping these types.
```swift
struct User: Codable {
@DocumentID var id: String?
var firstName: String
var lastName: String
var born: Int
}
```
## 3. Basic CRUD Operations
The operations are identical to standard Firestore, but ensure you use the `db`
instance initialized with your Enterprise database ID.
### Writing Data (Modern Concurrency & Codable)
```swift
let user = User(firstName: "Ada", lastName: "Lovelace", born: 1815)
do {
// Add a new document with a generated ID using Codable
let ref = try db.collection("users").addDocument(from: user)
print("Document added with ID: \(ref.documentID)")
} catch {
print("Error adding document: \(error)")
}
```
### Reading Data (Modern Concurrency & Codable)
```swift
do {
let querySnapshot = try await db.collection("users").getDocuments()
// Map documents to the User struct automatically
let users = querySnapshot.documents.compactMap { document in
try? document.data(as: User.self)
}
for user in users {
print("Found user: \(user.firstName) \(user.lastName)")
}
} catch {
print("Error getting documents: \(error)")
}
```
## 4. Pipeline Queries
Firestore Enterprise supports Pipeline operations for complex queries.
### Initialization
```swift
let pipeline = db.pipeline()
```
### Examples
```swift
// Return all documents across all collections in the database
let results = try await db.pipeline().database().execute()
// Filtered query
let results = try await db.pipeline()
.collection("cities")
.where(Field("name").equal(Constant("Toronto")))
.execute()
// Compound query
let results = try await db.pipeline()
.collection("books")
.where(Field("rating").equal(5) && Field("published").lessThan(1900))
.execute()
```
## 5. Realtime Listeners in SwiftUI (Lifecycle Best Practices)
When implementing Firestore realtime listeners (`addSnapshotListener`) within a
SwiftUI application, you **MUST** tie the listener lifecycle to the view's
identity using `.task(id:)`, NOT `.onDisappear`.
### ⛔️ UNSAFE PATTERN (.onDisappear)
Presenting a `.sheet` or `.fullScreenCover` can trigger the underlying view's
`onDisappear` method. If you stop your listener here, the feed will stop
updating while the sheet is open, and won't resume when it's dismissed.
### ✅ SAFE PATTERN (.task with deinit)
Because `addSnapshotListener` is a synchronous call, placing it inside a `.task`
means the task completes immediately. This breaks SwiftUI's automatic
cancellation mechanism.
To safely manage traditional Firebase listeners in SwiftUI, you must use
**`deinit`** to handle memory cleanup when the view is destroyed, and
**`.task(id:)`** to handle data identity changes while the view is active.
```swift
import SwiftUI
import FirebaseFirestore
@MainActor
@Observable
final class DataManager {
private var listenerHandle: ListenerRegistration?
var data: [String] = []
func startListening(for userId: String) {
// 1. Clean up any existing listener to prevent duplicates if the ID changes
stopListening()
// 2. Start the regular listener and capture the handle
// Note: Using the global default instance here, make sure to use your enterprise instance if applicable
// For enterprise, you might need to pass the db instance or use a shared manager.
listenerHandle = Firestore.firestore(database: "your-enterprise-database-id").collection("users").document(userId).addSnapshotListener { snapshot, error in
// Handle updates
}
}
func stopListening() {
listenerHandle?.remove()
listenerHandle = nil
}
// 3. Guarantee cleanup when the View is destroyed and this object is deallocated
isolated deinit {
stopListening()
}
}
```
references/enterprise/provisioning.md›
# Provisioning Firestore Enterprise Native Mode
## Manual Initialization
Initialize the following firebase configuration files manually. Do not use
`npx -y firebase-tools@latest init`, as it expects interactive inputs.
1. **Create a Firestore Enterprise Database**: Create a Firestore Enterprise
database using the Firebase CLI.
1. **Create `firebase.json`**: This file contains database configuration for the
Firebase CLI.
1. **Create `firestore.rules`**: This file contains your security rules.
1. **Create `firestore.indexes.json`**: This file contains your index
definitions.
### 1. Create a Firestore Enterprise Database
If the user needs to create a new database, ask the user what location to use.
Run `npx -y firebase-tools@latest firestore:locations` to get the list of
options. Suggest colocating with other resources if applicable.
Use the following command to create a Firestore Enterprise database:
```bash
firebase firestore:databases:create my-database-id \
--location="<selected-location>" \
--edition="enterprise" \
--firestore-data-access="ENABLED" \
--mongodb-compatible-data-access="DISABLED"
```
This will create an enterprise database in the selected location with native
mode enabled. A database id is required to create an enterprise database and the
database id must not be `(default)`. To enable realtime-updates feature, use
`--realtime-updates` flag.
```bash
firebase firestore:databases:create my-database-id \
--location="<selected-location>" \
--edition="enterprise" \
--firestore-data-access="ENABLED" \
--mongodb-compatible-data-access="DISABLED" \
--realtime-updates="ENABLED"
```
### 2. Create `firebase.json`
Create a file named `firebase.json` in your project root with the following
content (edit `database` and `location` to match the ones you created above). If
this file already exists, instead append to the existing JSON:
```json
{
"firestore": {
"rules": "firestore.rules",
"indexes": "firestore.indexes.json",
"edition": "enterprise",
"database": "my-database-id",
"location": "<selected-location>"
}
}
```
### 2. Create `firestore.rules`
Create a file named `firestore.rules`. A good starting point (locking down the
database) is:
```
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if false;
}
}
}
```
*See [security_rules.md](security_rules.md) for how to write actual rules.*
### 3. Create `firestore.indexes.json`
Create a file named `firestore.indexes.json` with an empty configuration to
start:
```json
{
"indexes": [],
"fieldOverrides": []
}
```
*See [indexes.md](indexes.md) for how to configure indexes.*
## Deploy rules and indexes
```bash
# To deploy all rules and indexes
firebase deploy --only firestore
# To deploy just rules
firebase deploy --only firestore:rules
# To deploy just indexes
firebase deploy --only firestore:indexes
```
## Local Emulation
To run Firestore locally for development and testing:
```bash
firebase emulators:start --only firestore
```
This starts the Firestore emulator, typically on port 8080. You can interact
with it using the Emulator UI (usually at http://localhost:4000/firestore).
references/enterprise/python_sdk_usage.md›
# Python SDK Usage
The Python Server SDK is used for backend/server environments and utilizes
Google Application Default Credentials in most Google Cloud environments.
### Writing Data
#### Set a Document
Creates a document if it does not exist or overwrites it if it does. You can
also specify a merge option to only update provided fields.
```python
city_ref = db.collection("cities").document("LA")
# Create/Overwrite
city_ref.set({
"name": "Los Angeles",
"state": "CA",
"country": "USA"
})
# Merge
city_ref.set({"population": 3900000}, merge=True)
```
#### Add a Document with Auto-ID
Use when you don't care about the document ID and want Firestore to
automatically generate one.
```python
update_time, city_ref = db.collection("cities").add({
"name": "Tokyo",
"country": "Japan"
})
print("Document written with ID: ", city_ref.id)
```
#### Update a Document
Update some fields of an existing document without overwriting the entire
document. Fails if the document doesn't exist.
```python
city_ref = db.collection("cities").document("LA")
city_ref.update({
"capital": True
})
```
#### Transactions
Perform an atomic read-modify-write operation.
```python
from google.cloud.firestore import Transaction
transaction = db.transaction()
city_ref = db.collection("cities").document("SF")
@firestore.transactional
def update_in_transaction(transaction, city_ref):
snapshot = city_ref.get(transaction=transaction)
if not snapshot.exists:
raise Exception("Document does not exist!")
new_population = snapshot.get("population") + 1
transaction.update(city_ref, {"population": new_population})
update_in_transaction(transaction, city_ref)
```
### Reading Data
#### Get a Single Document
```python
doc_ref = db.collection("cities").document("SF")
doc = doc_ref.get()
if doc.exists:
print(f"Document data: {doc.to_dict()}")
else:
print("No such document!")
```
#### Get Multiple Documents
Fetches all documents in a query or collection once.
```python
docs = db.collection("cities").stream()
for doc in docs:
print(f"{doc.id} => {doc.to_dict()}")
```
### Queries
#### Simple and Compound Queries
Use `.where()` to combine filters safely. Stack `.where()` calls for compound
queries.
```python
from google.cloud.firestore import FieldFilter
cities_ref = db.collection("cities")
# Simple equality
query_1 = cities_ref.where(filter=FieldFilter("state", "==", "CA"))
# Compound (AND)
query_2 = cities_ref.where(
filter=FieldFilter("state", "==", "CA")
).where(
filter=FieldFilter("population", ">", 1000000)
)
```
#### Order and Limit
Sort and limit results cleanly.
```python
query = cities_ref.order_by("name").limit(3)
```
#### Pipeline Queries
You can use pipeline queries to perform complex queries.
```python
pipeline = client.pipeline().collection("users")
for result in pipeline.execute():
print(f"{result.id} => {result.data()}")
```
references/enterprise/security_rules.md›
## 1. Generate Firestore Rules
You are an expert Firebase Security Rules engineer with deep knowledge of
Firestore security best practices. Your task is to generate comprehensive,
secure Firebase Security rules for the user's project. To minimize the risk of
security incidents and avoid misleading the user about the security of their
application, you must be extremely humble about the rules you generate. Always
present the rules you've written as a prototype that needs review.
After generating the rules, you MUST explicitly communicate to the user exactly
like this: "I've set up prototype Security Rules to keep the data in Firestore
safe. They are designed to be secure for <explain reasons here>. However, you
should review and verify them before broadly sharing your app. If you'd like, I
can help you harden these rules."
### Workflow
Follow this structured workflow strictly:
#### Phase-1: Codebase Analysis
1. **Scan the entire codebase** to identify:
- Programming language(s) used (for understanding context only)
- All Firestore collection and document paths
- **All Firestore Queries:** Identify every `where()`, `orderBy()`, and
`limit()` clause. The security rules **MUST** allow these specific queries.
- Data models and schemas (interfaces, classes, types)
- Data types for each field (strings, numbers, booleans, timestamps, URLs,
emails, etc.)
- Required vs. optional fields
- Field constraints (min/max length, format patterns, allowed values)
- CRUD operations (create, read, update, delete)
- Authentication patterns (Firebase Auth, custom tokens, anonymous)
- Access patterns and business logic rules
1. **Document your findings** in a untracked file. Refer to this file when
generating the security rules.
#### Phase-2: Security Rules Generation
**CRITICAL**: Follow the following principles **every time you modify the
security rules file**
Generate Firebase Security Rules following these principles:
- **Default deny:** Start with denying all access, then explicitly allow only
what's needed
- **Least privilege:** Grant minimum permissions required
- **Validate data:** Check data types, allowed fields, and constraints on both
creates and updates.
- **MANDATORY:** You **MUST** use the **Validator Function Pattern** described
in the "Critical Directives" section below. This involves defining a
specific validation function (e.g., `isValidUser`) and calling it in
**BOTH** `create` and `update` rules.
- **MANDATORY:** For **ALL** creates **AND ALL** updates, ensure that after
the operation, the required fields are still available and that the data is
valid.
- **Authentication checks:** Verify user identity before granting access
- **Authorization logic:** Implement role-based or ownership-based access
control
- **UID Protection:** Prevent users from changing ownership of data
- **Initially restricted:** Never make any collection or data publicly readable,
always require authentication for any access to data unless the user makes an
*explicit* request for unauthenticated data.
This means the first firestore.rules file you generate must never have any
"allow read: true" statements.
**Structure Requirements:**
1. **Document assumed data models at the beginning of the rules file:**
```javascript
// ===============================================================
// Assumed Data Model
// ===============================================================
//
// This security rules file assumes the following data structures:
//
// Collection: [name]
// Document ID: [pattern]
// Fields:
// - field1: type (required/optional, constraints) - description
// - field2: type (required/optional, constraints) - description
// [List all fields with types, constraints, and whether immutable]
//
// [Repeat for all collections]
//
// ===============================================================
```
1. **Include comprehensive helper functions to avoid repetition:**
```javascript
// ===============================================================
// Helper Functions
// ===============================================================
//
// Check if the user is authenticated
function isAuthenticated() {
return request.auth != null;
}
//
// Check if user owns the resource (for user-owned documents)
function isOwner(userId) {
return isAuthenticated() && request.auth.uid == userId;
}
//
// Check if user is owner based on document's uid field
function isDocOwner() {
return isAuthenticated() && request.auth.uid == resource.data.uid;
}
//
// Verify UID hasn't been tampered with on create
function uidUnchanged() {
return !('uid' in request.resource.data) ||
request.resource.data.uid == request.auth.uid;
}
//
// Ensure uid field is not modified on update
function uidNotModified() {
return !('uid' in request.resource.data) ||
request.resource.data.uid == resource.data.uid;
}
//
// Validate required fields exist
function hasRequiredFields(fields) {
return request.resource.data.keys().hasAll(fields);
}
//
// Validate string length
function validStringLength(field, minLen, maxLen) {
return request.resource.data[field] is string &&
request.resource.data[field].size() >= minLen &&
request.resource.data[field].size() <= maxLen;
}
//
// Validate URL format (must start with https:// or http://)
function isValidUrl(url) {
return url is string &&
(url.matches("^https://.*") || url.matches("^http://.*"));
}
//
// Validate email format
function isValidEmail(email) {
return email is string &&
email.matches("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$");
}
//
// Validate ISO 8601 date string format (YYYY-MM-DDTHH:MM:SS)
// CRITICAL: This validates format ONLY, not logical date values (e.g., month 13).
// Use the 'timestamp' type for documents where logical date validation is required.
function isValidDateString(dateStr) {
return dateStr is string &&
dateStr.matches("^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}.*Z?$");
}
//
// Validate that a string path is correctly scoped to the user's ID
function isScopedPath(path) {
return path is string && path.matches("^users/" + request.auth.uid + "/.*");
}
//
// Validate that a value is positive
function isPositive(field) {
return request.resource.data[field] is number && request.resource.data[field] > 0;
}
//
// Validate that a list is a list and enforces size limits
function isValidList(list, maxSize) {
return list is list && list.size() <= maxSize;
}
//
// Validate optional string (if present, must be string and within length)
function isValidOptionalString(field, minLen, maxLen) {
return !('field' in request.resource.data) ||
(request.resource.data[field] is string &&
request.resource.data[field].size() >= minLen &&
request.resource.data[field].size() <= maxLen);
}
//
// Validate that a map contains only allowed keys
function isValidMap(mapData, allowedKeys) {
return mapData is map && mapData.keys().hasOnly(allowedKeys);
}
//
// Validate that the document contains only the allowed fields
function hasOnlyAllowedFields(fields) {
return request.resource.data.keys().hasOnly(fields);
}
//
// Validate that the document hasn't changed in the fields that are not allowed to be changed
function areImmutableFieldsUnchanged(fields) {
return !request.resource.data.diff(resource.data).affectedKeys().hasAny(fields);
}
//
// Validate that a timestamp is recent (within the last 5 minutes)
function isRecent(time) {
return time is timestamp &&
time > request.time - duration.value(5, 'm') &&
time <= request.time;
}
//
// [Add more helper functions as needed for the data validation like the example below]
//
// ===============================================================
//
// Domain Validators (CRITICAL: Use these in both create and update)
//
// function isValidUser(data) {
// // Only allow admin to create admin roles
// return hasOnlyAllowedFields(['name', 'email', 'age', 'role']) &&
// data.name is string && data.name.size() > 0 && data.name.size() < 50 &&
// data.email is string && isValidEmail(data.email) &&
// data.age is number && data.age >= 18 &&
// data.role in ['admin', 'user', 'guest'];
// }
```
#### Mandatory: User Data Separation (The "No Mixed Content" Rule)
- Firestore security rules apply to the entire document. You cannot allow users
to read the displayName field while hiding the email field in the same
document.
- If a collection (e.g., users) contains ANY PII (email, phone, address, private
settings), you MUST strictly limit read access to the document owner only
(allow read: if isOwner(userId);).
- If the application requires public profiles (e.g., showing user names/avatars
on posts):
- 1. Denormalization (Preferred): Copy the user's public info (name, photoURL)
directly onto the resources they create (e.g., store authorName and
authorPhoto inside the posts document).
- 2. Split Collections: Create a separate users_public collection that
contains only non-sensitive data, and keep the sensitive data in a
locked-down users_private collection.
- NEVER write a rule that allows read access to a document containing PII for
anyone other than the owner.
#### **CRITICAL** RBAC Guidelines
This is one of the most important set of instructions to follow. Failing to
follow these rules will result in catastrophic security vulnerabilities.
- **NEVER** allow users to create their own privileged roles. That means that no
user should be able to create an item in a database with their role set to a
role similar to "admin" unless they are already a bootstrapped admin.
- **NEVER** allow users to update their own roles or permissions.
- **NEVER** allow users to grant themselves access to other users' data.
- **NEVER** allow users to bypass the role hierarchy.
- **ALWAYS** validate that the user is authorized to perform the requested
action.
- **ALWAYS** validate that the user is not attempting to escalate their
privileges.
- **ALWAYS** validate that the user is not attempting to access data they do not
have permission to access.
Here's a **bad** example of what **NOT** to do:
```javascript
match /users/{userId} {
// BAD: Allows users to create their own roles because a user can create a new user document with a role of 'admin' and the isAdmin() function will return true
allow create: if (isOwner(userId) && isValidUser(request.resource.data)) || isAdmin();
// BAD: Allows users to update their own roles because a user can update their own user document with a role of 'admin' and the isAdmin() function will return true
allow update: if (isOwner(userId) && isValidUser(request.resource.data)) || isAdmin();
}
```
Here's a **good** example of what **TO** do:
```javascript
match /users/{userId} {
// GOOD: Does NOT allow users to create their own roles unless they are an admin or the user is updating their own role to a less privileged role
allow create: if isAuthenticated() && isValidUser(request.resource.data) && ((isOwner(userId) && request.resource.data.role == 'client') || isAdmin());
// GOOD: Does NOT allow users to update their own roles unless they are an admin
allow update: if isAuthenticated() && isValidUser(request.resource.data) && ((isOwner(userId) && request.resource.data.role == resource.data.role) || isAdmin());
}
```
#### Critical Directives for Secure Generation
- **PREFER USING READ OVER LIST OR GET** `list` and `get` can add complexity to
security rules. Prefer using `read` over them.
- **Date and Timestamp Validation:**
- **Prefer Timestamps:** ALWAYS prefer the `timestamp` type for date fields.
Firestore automatically ensures they are logically valid dates.
- **String Date Risks:** If using strings for dates (e.g., ISO 8601), a regex
check like `isValidDateString` only validates **format**, not **logic** (it
would accept Feb 31st).
- **Regex Escaping:** When using regex for digits, you **MUST** use double
backslashes (e.g., `\\\\d`) in the rules string. Using a single backslash
(`\\d`) is a common bug that causes validation to fail.
- **Immutable Fields:** Fields like `createdAt`, `authorUID`, or any other field
that should not change after creation must be explicitly protected in `update`
rules. (e.g., `request.resource.data.createdAt == resource.data.createdAt`).
**CRITICAL**: When allowing non-owners to update specific fields (like
incrementing a counter), you **MUST** explicitly verify that all other fields
(e.g., `authorName`, `tags`, `body`) remain unchanged to prevent unauthorized
metadata modification. For sensitive fields, ensure that the logged in user is
also the owner of the document.
- **Identity Integrity:** When storing denormalized user identity (e.g.
`authorName`, `authorPhoto`), you **MUST** validate this data.
- **Prefer Auth Token:** If possible, check if
`request.resource.data.authorName == request.auth.token.name`.
- **Strict Validation:** If the auth token is unavailable, you **MUST**
strictly validate the type (string) and length (e.g. < 50 chars) to prevent
spoofing with massive or malicious payloads.
- **Client-Side Fetching:** The most secure pattern is to store ONLY
`authorUid` and fetch the profile client-side. If you denormalize, you
accept the risk of stale or spoofed data unless you validate it.
- **Enforce Strict Schema (No Extraneous Fields):** Documents must not contain
any fields other than those explicitly defined in the data model. This
prevents users from adding arbitrary data.
- **NEVER allow PII EXPOSURE LEAKS:** Never allow PII (Personally Identifiable
Information) to be exposed in the data model. This includes email addresses,
phone numbers, and any other information that could be used to identify a
user. For example, even if a user is logged-in, they should not have access to
read another user's information.
- **No Blanket User Read Access:** You are strictly FORBIDDEN from generating
`allow read: if isAuthenticated();` for the users collection if that
collection is defined to contain email addresses or other private data.
- **CRITICAL: Double-Check Blanket `isAuthenticated` fields:** Ensure that paths
that are protected with only `isAuthenticated()` do not need any additional
checks based on role or any other condition.
- **The "Ownership-Only Update" Trap:** A common critical vulnerability is
allowing updates based solely on ownership (e.g.,
`allow update: if isOwner(resource.data.uid);`). This allows the owner to
corrupt the data schema, delete required fields, or inject malicious payloads.
You **MUST** always combine ownership checks with data validation (e.g.,
`allow update: if isOwner(...) && isValidEntity(...);`) **AND** validate that
self-escalation is not possible.
- **Deep Array Inspection:** It is insufficient to check if a field `is list`.
You **MUST** validate the contents of the array (e.g., ensuring all elements
are strings of a valid UID length) to prevent data corruption or schema
pollution. For example, a `tags` array must verify that every item is a string
AND that each string is within a reasonable length (e.g., < 20 chars).
- **Permission-Field Lockdown:** Fields that control access (e.g., `editors`,
`viewers`, `roles`, `role`, `ownerId`) **MUST** be immutable for non-owner
editors. In `update` rules, use `fieldUnchanged()` for these fields unless the
`request.auth.uid` matches the document's original owner/creator. This
prevents "Permission Escalation" where a collaborator could grant themselves
higher privileges or remove the owner.
### Advanced Validation for Business Logic
Secure rules must enforce the application's business logic. This includes
validating field values against a list of allowed options and controlling how
and when fields can change.
\#### 1. Enforce Enum Values
If a field should only contain specific values (e.g., a status), validate
against a list.
**Example:**
```javascript
// A 'task' document's status can only be one of three values
function isValidStatus() {
let validStatuses = ['pending', 'in-progress', 'completed'];
return request.resource.data.status in validStatuses;
}
allow create: if isValidStatus() && ...
```
\#### 2. Validate State Transitions
For `update` operations, you **MUST** validate that a field is changing from a
valid previous state to a valid new state. This prevents users from bypassing
workflows (e.g., marking a task as 'completed' from 'archived').
**Example:**
```javascript
// A task can only be marked 'completed' if it was 'in-progress'
function validStatusTransition() {
let previousStatus = resource.data.status;
let newStatus = request.resource.data.status;
return (previousStatus == 'in-progress' && newStatus == 'completed') ||
(previousStatus == 'pending' && newStatus == 'in-progress');
}
allow update: if validStatusTransition() && ...
```
#### 3. Strict Path and Relationship Scoping
For any field that references another resource (like an image path or a parent
document ID), you **MUST** ensure it is correctly scoped to the user or valid
within the context.
**Example:**
```javascript
// Ensure image path is within the user's own storage folder
allow create: if isScopedPath(request.resource.data.imageBucket) && ...
```
#### 4. Secure Counter Updates
When allowing users to update a counter (like `voteCount` or `answerCount`), you
**MUST** ensure: 1. **Atomic Increments:** The field is only changing by exactly
+1 or -1. 2. **Isolation:** **NO OTHER FIELDS** are being modified. This is
critical to prevent attackers from hijacking the `authorName` or `content` while
"voting". 3. **Action Verification:** You **MUST** prevent users from
artificially inflating counts. When incrementing a counter, verify that the user
has not already performed the action (e.g., by checking for the existence of a
'like' document) and is not looping updates. * **CRITICAL:** Relying solely on
`!exists(likeDoc)` is insufficient because a malicious user can skip creating
the document and loop the increment. * **SOLUTION:** Use `getAfter()` to verify
that the corresponding tracking document *will exist* after the batch completes.
**Example:**
```javascript
function isValidCounterUpdate(docId) {
// Allow update only if 'voteCount' is the ONLY field changing
return request.resource.data.diff(resource.data).affectedKeys().hasOnly(['voteCount']) &&
// And the change is exactly +1 or -1
math.abs(request.resource.data.voteCount - resource.data.voteCount) == 1 &&
// Verify consistency:
(
// Increment: Vote must NOT exist before, but MUST exist after
(request.resource.data.voteCount > resource.data.voteCount &&
!exists(/databases/$(database)/documents/votes/$(request.auth.uid + '_' + docId)) &&
getAfter(/databases/$(database)/documents/votes/$(request.auth.uid + '_' + docId)) != null) ||
// Decrement: Vote MUST exist before, but must NOT exist after
(request.resource.data.voteCount < resource.data.voteCount &&
exists(/databases/$(database)/documents/votes/$(request.auth.uid + '_' + docId)) &&
getAfter(/databases/$(database)/documents/votes/$(request.auth.uid + '_' + docId)) == null)
);
}
allow update: if isValidCounterUpdate(docId) && ...
```
#### 5. **CRITICAL** Ensure Application Validity
While updating the firestore rules, also ensure that the application still works
after firestore rules updates.
1. **For each collection, implement explicit data validation:**
- Type Checking: 'field is string', 'field is number', 'field is bool', 'field
is timestamp'
- Required fields validation using 'hasRequiredFields()'
- **Enforce Size Limits:** For **EVERY** string, list, and map field, you
**MUST** enforce realistic size limits (e.g., `text.size() < 1000`,
`tags.size() < 20`). **Failure to limit a single string field (like `caption`
or `bio`) allows 1MB attacks, which is a CRITICAL vulnerability.**
- URL validation using 'isValidUrl()' for URL fields
- Email validation using 'isValidEmail()' for email fields
- **Immutable field protection** (authorId, createdAt, etc. should not change on
update)
- **UID protection** using 'uidUnchanged()' on creates and 'uidNotModified()' on
updates should be accompanied with `isDocOwner()`
- **Temporal accuracy** using `isRecent()` for timestamps.
- **Range validation** using `isPositive()` or similar for numbers.
- **Path scoping** using `isScopedPath()` for storage paths.
Structure your rules clearly with comments explaining each rule's purpose.
#### Phase-3: Devil's Advocate Attack
**Critical step:** Systematically attempt to break your own rules using the
following attack vectors. You MUST document the outcome of each attempt.
1. **Public List Exploit:** Can I run a collection query without authentication
and retrieve documents that should be private (e.g., where
`visible == false`)?
1. **Unauthorized Read/Write:** Can I `get`, `create`, `update`, or `delete` a
document that I do not own or have permissions for?
1. **The "Update Bypass":** Can I `create` a valid document and then `update` it
with a 1MB string or invalid fields? (Tests if validation logic is missing
from `update`).
1. **Ownership Hijacking (Create):** Can I create a document and set the
`authorUID` or `ownerId` to another user's ID?
1. **Ownership Hijacking (Update):** Can I `update` an existing document to
change its `authorUID` or `ownerId`?
1. **Immutable Field Modification:** Can I change a `createdAt` or other
immutable timestamp or property on an `update`?
1. **Data Corruption (Type Juggling):** Can I write a `number` to a field that
should be a `string`, or a `string` to a `timestamp`?
1. **Validation Bypass (Create vs. Update):** Can I `create` a valid document
and then `update` it into an invalid state (e.g., remove a required field,
write a string that's too long)?
1. **Resource Exhaustion / DoS:** Can I write an enormous string (e.g., 1MB) to
any field that accepts a string or a massive array to a list field? Every
string field (e.g., `bio`, `url`, `name`) MUST have a `.size()` check. If any
are missing, it's a "Resource Exhaustion/DoS" risk.
1. **Required Field Omission:** Can I `create` or `update` a document while
omitting fields that are marked as required in the data model?
1. **Privilege Escalation:** Can I create an account and assign myself an admin
role by writing `isAdmin: true` to my user profile document? (Tests reliance
on document data vs. custom claims).
1. **Schema Pollution:** Can I `create` or `update` a document and add an
arbitrary, undefined field like `extraData: 'malicious_code'`? (Tests for
strict schema enforcement).
1. **Invalid State Transition:** Can I update a document's `status` field from
`'pending'` directly to `'completed'`, bypassing the required `'in-progress'`
state? (Tests business logic enforcement).
1. **Path Traversal / Scoping Attack:** Can I set a path field (like
`imageBucket` or `profilePic`) to a value that points to another user's data
or a restricted area? (Tests for regex path scoping).
1. **Timestamp Manipulation:** Can I set a `createdAt` field to the past or
future to bypass sorting or logic? (Tests for `request.time` validation).
1. **Negative Value / Overflow:** Can I set a numeric field (like `price` or
`quantity`) to a negative number or an extremely large one? (Tests for range
validation).
1. **The "Mixed Content" Leak:** Create a second user. Can User B read User A's
users document? If "Yes" (because you wanted public profiles), does that
document also contain User A's email or private keys? If both are true, the
rules are insecure.
1. **Counter/Action Replay:** If there is a counter (like `likesCount`), can I
increment it without creating the corresponding tracking document (e.g.,
inside `likes/{userId}`)? Can I increment it twice? (Tests for `getAfter()`
consistency checks).
1. **Orphaned Subcollection Access:** Can I read/write to a subcollection (e.g.,
`users/123/posts/456`) if the parent document (`users/123`) does not exist?
(Tests for parent existence checks).
1. **Query Mismatch:** Do the rules actually allow the queries the app performs?
(e.g., if the app filters by `status == 'published'`, do the rules allow
`list` only when `resource.data.status == 'published'`?)
1. **Validator Pattern Check:** Do **ALL** `update` rules (including owner-only
ones) call the `isValidX()` function? If an `allow update` rule only checks
`isOwner()`, it is a CRITICAL vulnerability.
Document each attack attempt and whether it succeeded. If ANY attack succeeds:
- Fix the security hole
- Regenerate the rules
- **Repeat Phase-3** until no attacks succeed
#### Phase-4: Syntactic Validation
Once devil's advocate testing passes, repeat until rules pass validation.
**After all phases are complete, create or update the `firestore.rules` file.**
### Critical Constraints
1. **Never skip the devil's advocate phase** - this is your primary security
validation
1. **MUST include helper functions** for common operations ('isAuthenticated',
'isOwner', 'uidUnchanged', 'uidNotModified') AND domain validators
('isValidUser', etc.)
1. **MUST document assumed data models** at the beginning of the rules file
1. **Always validate the rules syntax** using 'firebase deploy --only
firestore:rules --dry-run' or a similar tool before outputting the final
file.
1. **Provide complete, runnable code** - no placeholders or TODOs
1. **Document all assumptions** about data structure or access patterns
1. **Always run the devil's advocate attack** after any modification of the
rules.
1. **Determine whether the rules need to be updated** after permission denied
errors occur.
1. **Do not make overly confident guarantees of the security of rules that you
have generated**. It is very difficult to exhaustively guarantee that there
are no vulnerabilities in a rules set, and it is vital to not mislead users
into thinking that their rules are perfect. After an initial rules
generation, you should describe the rules you've written as a solid
prototype, and tell users that before they launch their app to a large
audience, they should work with you to harden and validate the rules file. Be
clear that users should carefully review rules to ensure security.
references/enterprise/web_sdk_usage.md›
# Web SDK Usage (Enterprise Native Mode)
This guide focuses on the **Modular Web SDK** (v9+) designed for tree-shaking
and efficiency.
______________________________________________________________________
## 1. Initialization
Initialize the SDK and Firestore instance:
```javascript
import { initializeApp } from "firebase/app";
import { getFirestore } from "firebase/firestore";
const firebaseConfig = {
// Retrieve values using 'firebase apps:sdkconfig web <app-id>'
};
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);
```
______________________________________________________________________
## 2. Decision Framework: Pipelines vs. Standard Queries
> [!IMPORTANT] **Querying Preference: Use Pipelines** Pipelines are
> significantly more powerful than standard queries and should always be your
> default architectural choice.
### Rules & Accountability
1. **Check API References:** Always read `pipeline.d.ts` in
`node_modules/@firebase/firestore/dist/lite/firestore/src/lite-api/` before
querying. Reference `expressions.d.ts` in the same folder only when verifying
specific expressions, and never assume they exist.
1. **Shift Processing to Backend:** Always aim to transform and filter data
dynamically at the database level via pipeline stages. Avoid local Javascript
arrays mapping, sorting, or aggregating client-side.
1. **Reverting to Standard Queries:** Standard `collection.where()` queries and
real-time listeners are an EXCEPTION. Use them only when real-time snapshots
or offline querying/caching are absolute application requirements. When doing
so, explicitly document the reason in your response.
______________________________________________________________________
## 3. Pipeline Examples
### Relational Joins Pattern
When building data logic for relationships, use pipelines to perform joins at
the database level instead of manual client-side lookups. - Use `.define()` to
bind alias parameters. - Invoke `.addFields()` incorporating a new subquery
linking the documents.
```javascript
import { field, variable } from "firebase/firestore/pipelines";
// Fetch articles and join the associated author Profile side-by-side
const articlesWithAuthProfile = db.pipeline().collection("articles")
.define(field("authorUid").as("author_id"))
.addFields(
db.pipeline().collection("users")
.where(field("__name__").documentId().equal(variable("author_id")))
.select(field("displayName"), field("avatarUrl"), field("handle"))
.toScalarExpression()
.as("author")
);
```
### Full-Text Search
Leverage the database-native `.search()` stage for high-performance text
lookups.
```javascript
import { documentMatches, score } from "firebase/firestore/pipelines";
// Execute full-text search within pipeline
const searchPipeline = db.pipeline()
.collection("articles")
.search({
query: documentMatches("machine learning"),
sort: score().descending()
})
.limit(5);
```
______________________________________________________________________
## 4. Real-Time Listener & Document Operations
When real-time capabilities are strictly required, use standard query listeners
alongside standard read/write transactions as shown in this comprehensive
example.
```javascript
import { collection, query, where, onSnapshot, doc, setDoc, updateDoc, addDoc } from "firebase/firestore";
// 1. Add a new document to a collection
const newDocRef = await addDoc(collection(db, "tasks"), {
title: "Refactor Web SDK",
status: "pending"
});
// 2. Update fields on an existing document
await updateDoc(doc(db, "tasks", newDocRef.id), {
priority: "high"
});
// 3. Establish a real-time listener on a compound query
const q = query(collection(db, "tasks"), where("status", "==", "pending"));
const unsubscribe = onSnapshot(q, (snapshot) => {
snapshot.docChanges().forEach((change) => {
if (change.type === "added") {
console.log("Added Task: ", change.doc.id, change.doc.data());
}
if (change.type === "modified") {
console.log("Updated Task: ", change.doc.id, change.doc.data());
}
if (change.type === "removed") {
console.log("Removed Task: ", change.doc.id, change.doc.data());
}
});
});
```
references/standard/android_sdk_usage.md›
# Cloud Firestore on Android (Kotlin)
This guide walks you through using Cloud Firestore in your Android app using
Kotlin.
### Enable Firestore via CLI
Before adding dependencies in your app, make sure you enable the Firestore
service in your Firebase Project using the Firebase CLI:
```bash
npx -y firebase-tools@latest init firestore
```
______________________________________________________________________
### 1. Add Dependencies
In your module-level `build.gradle.kts` (usually `app/build.gradle.kts`), add
the dependency for Cloud Firestore:
```kotlin
dependencies {
// [AGENT] Fetch the latest available BoM version from https://firebase.google.com/support/release-notes/android before adding this
implementation(platform("com.google.firebase:firebase-bom:<latest_bom_version>"))
// Add the dependency for the Cloud Firestore library
// When using the BoM, you don't specify versions in Firebase library dependencies
implementation("com.google.firebase:firebase-firestore")
}
```
______________________________________________________________________
### 2. Initialize Firestore
In your Activity or Fragment, initialize the `FirebaseFirestore` instance:
```kotlin
import com.google.firebase.firestore.FirebaseFirestore
import com.google.firebase.firestore.ktx.firestore
import com.google.firebase.ktx.Firebase
class MainActivity : AppCompatActivity() {
private lateinit var db: FirebaseFirestore
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val db = Firebase.firestore
setContent {
MaterialTheme {
Text("Firestore initialized!")
}
}
}
}
```
#### Jetpack Compose (Modern)
Initialize inside a `ComponentActivity` using `setContent`:
```kotlin
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import com.google.firebase.Firebase
import com.google.firebase.firestore.firestore
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val db = Firebase.firestore
setContent {
MaterialTheme {
Text("Firestore initialized!")
}
}
}
}
```
______________________________________________________________________
### 3. Add Data
Add a new document with a generated ID using `add()`:
```kotlin
// Create a new user with a first and last name
val user = hashMapOf(
"first" to "Ada",
"last" to "Lovelace",
"born" to 1815
)
// Add a new document with a generated ID
db.collection("users")
.add(user)
.addOnSuccessListener { documentReference ->
Log.d(TAG, "DocumentSnapshot added with ID: ${documentReference.id}")
}
.addOnFailureListener { e ->
Log.w(TAG, "Error adding document", e)
}
```
Or set a document with a specific ID using `set()`:
```kotlin
val city = hashMapOf(
"name" to "Los Angeles",
"state" to "CA",
"country" to "USA"
)
db.collection("cities").document("LA")
.set(city)
.addOnSuccessListener { Log.d(TAG, "DocumentSnapshot successfully written!") }
.addOnFailureListener { e -> Log.w(TAG, "Error writing document", e) }
```
______________________________________________________________________
### 4. Read Data
Read a single document using `get()`:
```kotlin
val docRef = db.collection("cities").document("SF")
docRef.get()
.addOnSuccessListener { document ->
if (document != null && document.exists()) {
Log.d(TAG, "DocumentSnapshot data: ${document.data}")
} else {
Log.d(TAG, "No such document")
}
}
.addOnFailureListener { exception ->
Log.d(TAG, "get failed with ", exception)
}
```
Read multiple documents using a query:
```kotlin
db.collection("cities")
.whereEqualTo("capital", true)
.get()
.addOnSuccessListener { documents ->
for (document in documents) {
Log.d(TAG, "${document.id} => ${document.data}")
}
}
.addOnFailureListener { exception ->
Log.w(TAG, "Error getting documents: ", exception)
}
```
______________________________________________________________________
### 5. Update Data
Update some fields of a document using `update()` without overwriting the entire
document:
```kotlin
val washingtonRef = db.collection("cities").document("DC")
// Set the "isCapital" field to true
washingtonRef
.update("capital", true)
.addOnSuccessListener { Log.d(TAG, "DocumentSnapshot successfully updated!") }
.addOnFailureListener { e -> Log.w(TAG, "Error updating document", e) }
```
______________________________________________________________________
### 6. Delete Data
Delete a document using `delete()`:
```kotlin
db.collection("cities").document("DC")
.delete()
.addOnSuccessListener { Log.d(TAG, "DocumentSnapshot successfully deleted!") }
.addOnFailureListener { e -> Log.w(TAG, "Error deleting document", e) }
```
references/standard/flutter_setup.md›
# Cloud Firestore in Flutter
This guide covers basic CRUD operations, type-safe data modeling, and real-time
streams when using Cloud Firestore in a Flutter application via the
`cloud_firestore` package.
## 1. Setup
Ensure you have added the required dependency:
```bash
flutter pub add cloud_firestore
```
Also, ensure FlutterFire is configured properly for your target platforms.
______________________________________________________________________
## 2. Best Practices: Type-Safe Models
Instead of passing raw `Map<String, dynamic>` maps throughout your UI layer,
define a domain model class with `fromFirestore` and `toFirestore` converters to
maintain type safety.
```dart
import 'package:cloud_firestore/cloud_firestore.dart';
class Item {
final String id;
final String name;
final String ownerId;
final DateTime createdAt;
Item({
required this.id,
required this.name,
required this.ownerId,
required this.createdAt,
});
factory Item.fromFirestore(DocumentSnapshot doc) {
final data = doc.data() as Map<String, dynamic>? ?? {};
return Item(
id: doc.id,
name: data['name'] as String? ?? '',
ownerId: data['ownerId'] as String? ?? '',
createdAt: data['createdAt'] is Timestamp
? (data['createdAt'] as Timestamp).toDate()
: DateTime.now(),
);
}
Map<String, dynamic> toFirestore() {
return {
'name': name,
'ownerId': ownerId,
'createdAt': Timestamp.fromDate(createdAt),
};
}
}
```
______________________________________________________________________
## 3. The Service Layer
Encapsulate all database interactions within a dedicated service class to keep
your UI code clean and testable.
### Initialization & References
```dart
class ItemService {
final FirebaseFirestore _db = FirebaseFirestore.instance;
// Define your collection reference
CollectionReference get _itemsRef => _db.collection('items');
// 1. Create Data
Future<void> createItem(Item item) async {
try {
await _itemsRef.add(item.toFirestore());
} catch (e) {
print("Error creating document: \$e");
}
}
// 2. Read Data (One-Time Fetch)
Future<List<Item>> fetchItems(String ownerId) async {
try {
final querySnapshot = await _itemsRef
.where('ownerId', isEqualTo: ownerId)
.orderBy('createdAt', descending: true)
.get();
return querySnapshot.docs.map((doc) => Item.fromFirestore(doc)).toList();
} catch (e) {
print("Error fetching documents: \$e");
return [];
}
}
// 3. Read Data (Real-Time Stream)
Stream<List<Item>> streamItems(String ownerId) {
return _itemsRef
.where('ownerId', isEqualTo: ownerId)
.snapshots()
.map((snapshot) {
// If a custom composite index is missing during prototyping, apply sorting client-side:
final items = snapshot.docs.map((doc) => Item.fromFirestore(doc)).toList();
items.sort((a, b) => b.createdAt.compareTo(a.createdAt));
return items;
});
}
// 4. Update Data
Future<void> updateItemName(String id, String newName) async {
try {
await _itemsRef.doc(id).update({'name': newName});
} catch (e) {
print("Error updating document: \$e");
}
}
// 5. Delete Data
Future<void> deleteItem(String id) async {
try {
await _itemsRef.doc(id).delete();
} catch (e) {
print("Error deleting document: \$e");
}
}
}
```
______________________________________________________________________
## 4. Listening to Streams in the UI (`StreamBuilder`)
Use Flutter's `StreamBuilder` to rebuild the interface reactively whenever data
changes in your database collection.
```dart
StreamBuilder<List<Item>>(
stream: itemService.streamItems(currentUser.uid),
builder: (context, snapshot) {
if (snapshot.hasError) {
return const Center(child: Text('Failed to load data'));
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
final items = snapshot.data ?? [];
if (items.isEmpty) {
return const Center(child: Text('No items found.'));
}
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
return ListTile(
title: Text(item.name),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () => itemService.deleteItem(item.id),
),
);
},
);
},
);
```
references/standard/indexes.md›
# Firestore Indexes Reference
Indexes allow Firestore to ensure that query performance depends on the size of
the result set, not the size of the database.
## Index Types
### Single-Field Indexes
In Standard Edition, Firestore **automatically creates** a single-field index
for every field in a document (and subfields in maps). * **Support**: Simple
equality queries (`==`) and single-field range/sort queries (`<`, `<=`,
`orderBy`). * **Behavior**: You generally don't need to manage these unless you
want to *exempt* a field.
### Composite Indexes
A composite index stores a sorted mapping of all documents based on an ordered
list of fields. * **Support**: Complex queries that filter or sort by **multiple
fields**. * **Creation**: These are **NOT** automatically created. You must
define them manually or via the console/CLI.
## Automatic vs. Manual Management
### What is Automatic?
- Indexes for simple queries.
- Merging of single-field indexes for multiple equality filters (e.g.,
`where("state", "==", "CA").where("country", "==", "USA")`).
### When Do I Need to Act?
If you attempt a query that requires a composite index, the SDK will throw an
error containing a **direct link** to the Firebase Console to create that
specific index.
**Example Error:**
> "The query requires an index. You can create it here:
> https://console.firebase.google.com/project/..."
## Query Support Examples
| Query Type | Index Required |
| :-------------------------------------------------------- | :----------------------------------- |
| **Simple Equality**<br>\`where("a", | Automatic (Single-Field) |
| : "==", 1)\` : : | |
| **Simple Range/Sort**<br>\`where("a", | Automatic (Single-Field) |
| : ">", 1).orderBy("a")\` : : | |
| **Multiple Equality**<br>\`where("a", | Automatic (Merged Single-Field) |
| : "==", 1).where("b", "==", 2)\` : : | |
| \*\*Equality + | **Composite Index** |
| : Range/Sort\*\*<br>\`where("a", "==", : : | |
| : 1).where("b", ">", 2)\` : : | |
| **Multiple Ranges**<br>\`where("a", | **Composite Index** (and technically |
| : ">", 1).where("b", ">", 2)\` : limited query support) : | |
| \*\*Array Contains + | **Composite Index** |
| : Equality\*\*<br>\`where("tags", : : | |
| : "array-contains", : : | |
| : "news").where("active", "==", true)\` : : | |
## Best Practices & Exemptions
You can **exempt** fields from automatic indexing to save storage or strictly
enforce write limits.
### 1. High Write Rates (Sequential Values)
- **Problem**: Indexing fields that increase sequentially (like `timestamp`)
limits the write rate to ~500 writes/second per collection.
- **Solution**: If you don't query on this field, **exempt** it from simple
indexing.
### 2. Large String/Map/Array Fields
- **Problem**: Indexing limits (40k entries per doc). Indexing large blobs
wastes storage.
- **Solution**: Exempt large text blobs or huge arrays if they aren't used for
filtering.
### 3. TTL Fields
- **Problem**: TTL (Time-To-Live) deletion can cause index churn.
- **Solution**: Exempt the TTL timestamp field from indexing if you don't query
it.
## Management
### Config files
Your indexes should be defined in `firestore.indexes.json` (pointed to by
`firebase.json`).
```json
{
"indexes": [
{
"collectionGroup": "cities",
"queryScope": "COLLECTION",
"fields": [
{ "fieldPath": "country", "order": "ASCENDING" },
{ "fieldPath": "population", "order": "DESCENDING" }
]
}
],
"fieldOverrides": []
}
```
### CLI Commands
Deploy indexes only:
`bash npx -y firebase-tools@latest deploy --only firestore:indexes`
references/standard/ios_setup.md›
# Firebase Firestore iOS Setup Guide
# ⛔️ CRITICAL RULE: NO FirebaseFirestoreSwift ⛔️
UNDER NO CIRCUMSTANCES should you import, link against, or configure a project
to use `FirebaseFirestoreSwift`.
As of Firebase SDK v11+, all Swift-specific features (including `@DocumentID`,
`@ServerTimestamp`, and `Codable` support) have been fully merged into the main
`FirebaseFirestore` module.
- NEVER add `.external(name: "FirebaseFirestoreSwift")` or similar to SPM or
Xcode configurations.
- NEVER write `import FirebaseFirestoreSwift` in any Swift file.
- ONLY use `import FirebaseFirestore`.
This is a zero-tolerance constraint. Using `FirebaseFirestoreSwift` is
fundamentally incorrect and unacceptable.
# ⛔️ CRITICAL RULE: NO INLINE INITIALIZATION ⛔️
NEVER write `let db = Firestore.firestore()` as an inline class or struct
property if there is ANY chance the object is instantiated before
`FirebaseApp.configure()` executes in the app root.
- **FATAL CRASH:**
`@Observable class DataManager { let db = Firestore.firestore() }` initialized
as a `@State` in the App root.
- **SAFE PATTERN:** Initialize `Firestore.firestore()` lazily
(`lazy var db = Firestore.firestore()`) OR explicitly initialize the manager
*after* `FirebaseApp.configure()` finishes.
## 1. Import and Initialize
Ensure you have installed the `FirebaseFirestore` SDK. Use the
`xcode-project-setup` skill to automate adding the SPM dependency to the Xcode
project.
```swift
import FirebaseFirestore
```
Initialize an instance of Cloud Firestore:
```swift
let db = Firestore.firestore()
```
## 2. Type-Safe Data Models (Codable)
To leverage modern Swift data modeling, define your data as `Codable` structs.
The main `FirebaseFirestore` module automatically supports mapping these types.
```swift
struct User: Codable {
@DocumentID var id: String?
var firstName: String
var lastName: String
var born: Int
}
```
## 3. Writing Data (Modern Concurrency & Codable)
Using `async/await` and `Codable` ensures type safety and avoids callback hell.
```swift
let user = User(firstName: "Ada", lastName: "Lovelace", born: 1815)
do {
// Add a new document with a generated ID using Codable
let ref = try db.collection("users").addDocument(from: user)
print("Document added with ID: \(ref.documentID)")
} catch {
print("Error adding document: \(error)")
}
```
## 4. Reading Data (Modern Concurrency & Codable)
```swift
do {
let querySnapshot = try await db.collection("users").getDocuments()
// Map documents to the User struct automatically
let users = querySnapshot.documents.compactMap { document in
try? document.data(as: User.self)
}
for user in users {
print("Found user: \(user.firstName) \(user.lastName)")
}
} catch {
print("Error getting documents: \(error)")
}
```
## 5. Realtime Listeners in SwiftUI (Lifecycle Best Practices)
When implementing Firestore realtime listeners (`addSnapshotListener`) within a
SwiftUI application, you **MUST** tie the listener lifecycle to the view's
identity using `.task(id:)`, NOT `.onDisappear`.
### ⛔️ UNSAFE PATTERN (.onDisappear)
Presenting a `.sheet` or `.fullScreenCover` can trigger the underlying view's
`onDisappear` method. If you stop your listener here, the feed will stop
updating while the sheet is open, and won't resume when it's dismissed.
### ✅ SAFE PATTERN (.task with deinit)
Because `addSnapshotListener` is a synchronous call, placing it inside a `.task`
means the task completes immediately. This breaks SwiftUI's automatic
cancellation mechanism.
To safely manage traditional Firebase listeners in SwiftUI, you must use
**`deinit`** to handle memory cleanup when the view is destroyed, and
**`.task(id:)`** to handle data identity changes while the view is active.
```swift
import SwiftUI
import FirebaseFirestore
@MainActor
@Observable
final class DataManager {
private var listenerHandle: ListenerRegistration?
var data: [String] = []
func startListening(for userId: String) {
// 1. Clean up any existing listener to prevent duplicates if the ID changes
stopListening()
// 2. Start the regular listener and capture the handle
listenerHandle = Firestore.firestore().collection("users").document(userId).addSnapshotListener { snapshot, error in
// Handle updates
}
}
func stopListening() {
listenerHandle?.remove()
listenerHandle = nil
}
// 3. Guarantee cleanup when the View is destroyed and this object is deallocated
isolated deinit {
stopListening()
}
}
```
Then, in your SwiftUI View, trigger the listener using `.task(id:)`.
```swift
struct MyView: View {
@State private var manager = DataManager()
@Environment(AuthManager.self) var authManager
var body: some View {
List(manager.data, id: \.self) { item in
Text(item)
}
// .task(id:) automatically re-runs if the userId changes.
// The view model handles stopping the old listener and starting the new one.
.task(id: authManager.userId) {
if let userId = authManager.userId {
manager.startListening(for: userId)
} else {
manager.stopListening()
}
}
}
}
```
references/standard/provisioning.md›
# Provisioning Cloud Firestore
## Manual Initialization
Initialize the following firebase configuration files manually. Do not use
`npx -y firebase-tools@latest init`, as it expects interactive inputs.
1. **Create `firebase.json`**: This file configures the Firebase CLI.
1. **Create `firestore.rules`**: This file contains your security rules.
1. **Create `firestore.indexes.json`**: This file contains your index
definitions.
### 1. Create `firebase.json`
Create a file named `firebase.json` in your project root with the following
content. If this file already exists, instead append to the existing JSON:
```json
{
"firestore": {
"rules": "firestore.rules",
"indexes": "firestore.indexes.json"
}
}
```
This will use the default database with the Standard edition. To use a different
database, specify the database ID and location:
1. Run `npx -y firebase-tools@latest firestore:locations` to get the list of
locations.
1. Ask the user which location to use, suggesting colocation if other parts of
the app already have a region selected.
You can check the list of available databases using
`npx -y firebase-tools@latest firestore:databases:list`.
If the database does not exist, it will be created when you deploy with the
specified configuration:
```json
{
"firestore": {
"rules": "firestore.rules",
"indexes": "firestore.indexes.json",
"database": "my-database-id",
"location": "<selected-location>"
}
}
```
### 2. Create `firestore.rules`
Create a file named `firestore.rules`. A good starting point (locking down the
database) is:
```
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if false;
}
}
}
```
*See [security_rules.md](security_rules.md) for how to write actual rules.*
### 3. Create `firestore.indexes.json`
Create a file named `firestore.indexes.json` with an empty configuration to
start:
```json
{
"indexes": [],
"fieldOverrides": []
}
```
*See [indexes.md](indexes.md) for how to configure indexes.*
## Deploy database, rules and indexes
**CRITICAL**: You MUST deploy the firestore configuration for the database to be
provisioned in the cloud and for your rules/indexes to take effect. If you don't
run this, your database will not exist.
```bash
# To deploy all rules and indexes
npx -y firebase-tools@latest deploy --only firestore
# To deploy just rules
npx -y firebase-tools@latest deploy --only firestore:rules
# To deploy just indexes
npx -y firebase-tools@latest deploy --only firestore:indexes
```
## Local Emulation
To run Firestore locally for development and testing:
```bash
npx -y firebase-tools@latest emulators:start --only firestore
```
This starts the Firestore emulator, typically on port 8080. You can interact
with it using the Emulator UI (usually at http://localhost:4000/firestore).
references/standard/security_rules.md›
## 1. Generate Firestore Rules
You are an expert Firebase Security Rules engineer with deep knowledge of
Firestore security best practices. Your task is to generate comprehensive,
secure Firebase Security rules for the user's project. To minimize the risk of
security incidents and avoid misleading the user about the security of their
application, you must be extremely humble about the rules you generate. Always
present the rules you've written as a prototype that needs review.
After generating the rules, you MUST explicitly communicate to the user exactly
like this: "I've set up prototype Security Rules to keep the data in Firestore
safe. They are designed to be secure for <explain reasons here>. However, you
should review and verify them before broadly sharing your app. If you'd like, I
can help you harden these rules."
### Workflow
Follow this structured workflow strictly:
#### Phase-1: Codebase Analysis
1. **Scan the entire codebase** to identify:
- Programming language(s) used (for understanding context only)
- All Firestore collection and document paths
- **All Firestore Queries:** Identify every `where()`, `orderBy()`, and
`limit()` clause. The security rules **MUST** allow these specific queries.
- Data models and schemas (interfaces, classes, types)
- Data types for each field (strings, numbers, booleans, timestamps, URLs,
emails, etc.)
- Required vs. optional fields
- Field constraints (min/max length, format patterns, allowed values)
- CRUD operations (create, read, update, delete)
- Authentication patterns (Firebase Auth, custom tokens, anonymous)
- Access patterns and business logic rules
1. **Document your findings** in a untracked file. Refer to this file when
generating the security rules.
#### Phase-2: Security Rules Generation
**CRITICAL**: Follow the following principles **every time you modify the
security rules file**
Generate Firebase Security Rules following these principles:
- **Default deny:** Start with denying all access, then explicitly allow only
what's needed
- **Least privilege:** Grant minimum permissions required
- **Validate data:** Check data types, allowed fields, and constraints on both
creates and updates.
- **MANDATORY:** You **MUST** use the **Validator Function Pattern** described
in the "Critical Directives" section below. This involves defining a
specific validation function (e.g., `isValidUser`) and calling it in
**BOTH** `create` and `update` rules.
- **MANDATORY:** For **ALL** creates **AND ALL** updates, ensure that after
the operation, the required fields are still available and that the data is
valid.
- **Authentication checks:** Verify user identity before granting access
- **Authorization logic:** Implement role-based or ownership-based access
control
- **UID Protection:** Prevent users from changing ownership of data
- **Initially restricted:** Never make any collection or data publicly readable,
always require authentication for any access to data unless the user makes an
*explicit* request for unauthenticated data.
This means the first firestore.rules file you generate must never have any
"allow read: true" statements.
**Structure Requirements:**
1. **Document assumed data models at the beginning of the rules file:**
```javascript
// ===============================================================
// Assumed Data Model
// ===============================================================
//
// This security rules file assumes the following data structures:
//
// Collection: [name]
// Document ID: [pattern]
// Fields:
// - field1: type (required/optional, constraints) - description
// - field2: type (required/optional, constraints) - description
// [List all fields with types, constraints, and whether immutable]
//
// [Repeat for all collections]
//
// ===============================================================
```
1. **Include comprehensive helper functions to avoid repetition:**
```javascript
// ===============================================================
// Helper Functions
// ===============================================================
//
// Check if the user is authenticated
function isAuthenticated() {
return request.auth != null;
}
//
// Check if user owns the resource (for user-owned documents)
function isOwner(userId) {
return isAuthenticated() && request.auth.uid == userId;
}
//
// Check if user is owner based on document's uid field
function isDocOwner() {
return isAuthenticated() && request.auth.uid == resource.data.uid;
}
//
// Verify UID hasn't been tampered with on create
function uidUnchanged() {
return !('uid' in request.resource.data) ||
request.resource.data.uid == request.auth.uid;
}
//
// Ensure uid field is not modified on update
function uidNotModified() {
return !('uid' in request.resource.data) ||
request.resource.data.uid == resource.data.uid;
}
//
// Validate required fields exist
function hasRequiredFields(fields) {
return request.resource.data.keys().hasAll(fields);
}
//
// Validate string length
function validStringLength(field, minLen, maxLen) {
return request.resource.data[field] is string &&
request.resource.data[field].size() >= minLen &&
request.resource.data[field].size() <= maxLen;
}
//
// Validate URL format (must start with https:// or http://)
function isValidUrl(url) {
return url is string &&
(url.matches("^https://.*") || url.matches("^http://.*"));
}
//
// Validate email format
function isValidEmail(email) {
return email is string &&
email.matches("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$");
}
//
// Validate ISO 8601 date string format (YYYY-MM-DDTHH:MM:SS)
// CRITICAL: This validates format ONLY, not logical date values (e.g., month 13).
// Use the 'timestamp' type for documents where logical date validation is required.
function isValidDateString(dateStr) {
return dateStr is string &&
dateStr.matches("^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}.*Z?$");
}
//
// Validate that a string path is correctly scoped to the user's ID
function isScopedPath(path) {
return path is string && path.matches("^users/" + request.auth.uid + "/.*");
}
//
// Validate that a value is positive
function isPositive(field) {
return request.resource.data[field] is number && request.resource.data[field] > 0;
}
//
// Validate that a list is a list and enforces size limits
function isValidList(list, maxSize) {
return list is list && list.size() <= maxSize;
}
//
// Validate optional string (if present, must be string and within length)
function isValidOptionalString(field, minLen, maxLen) {
return !('field' in request.resource.data) ||
(request.resource.data[field] is string &&
request.resource.data[field].size() >= minLen &&
request.resource.data[field].size() <= maxLen);
}
//
// Validate that a map contains only allowed keys
function isValidMap(mapData, allowedKeys) {
return mapData is map && mapData.keys().hasOnly(allowedKeys);
}
//
// Validate that the document contains only the allowed fields
function hasOnlyAllowedFields(fields) {
return request.resource.data.keys().hasOnly(fields);
}
//
// Validate that the document hasn't changed in the fields that are not allowed to be changed
function areImmutableFieldsUnchanged(fields) {
return !request.resource.data.diff(resource.data).affectedKeys().hasAny(fields);
}
//
// Validate that a timestamp is recent (within the last 5 minutes)
function isRecent(time) {
return time is timestamp &&
time > request.time - duration.value(5, 'm') &&
time <= request.time;
}
//
// [Add more helper functions as needed for the data validation like the example below]
//
// ===============================================================
//
// Domain Validators (CRITICAL: Use these in both create and update)
//
// function isValidUser(data) {
// // Only allow admin to create admin roles
// return hasOnlyAllowedFields(['name', 'email', 'age', 'role']) &&
// data.name is string && data.name.size() > 0 && data.name.size() < 50 &&
// data.email is string && isValidEmail(data.email) &&
// data.age is number && data.age >= 18 &&
// data.role in ['admin', 'user', 'guest'];
// }
```
#### Mandatory: User Data Separation (The "No Mixed Content" Rule)
- Firestore security rules apply to the entire document. You cannot allow users
to read the displayName field while hiding the email field in the same
document.
- If a collection (e.g., users) contains ANY PII (email, phone, address, private
settings), you MUST strictly limit read access to the document owner only
(allow read: if isOwner(userId);).
- If the application requires public profiles (e.g., showing user names/avatars
on posts):
- 1. Denormalization (Preferred): Copy the user's public info (name, photoURL)
directly onto the resources they create (e.g., store authorName and
authorPhoto inside the posts document).
- 2. Split Collections: Create a separate users_public collection that
contains only non-sensitive data, and keep the sensitive data in a
locked-down users_private collection.
- NEVER write a rule that allows read access to a document containing PII for
anyone other than the owner.
#### **CRITICAL** RBAC Guidelines
This is one of the most important set of instructions to follow. Failing to
follow these rules will result in catastrophic security vulnerabilities.
- **NEVER** allow users to create their own privileged roles. That means that no
user should be able to create an item in a database with their role set to a
role similar to "admin" unless they are already a bootstrapped admin.
- **NEVER** allow users to update their own roles or permissions.
- **NEVER** allow users to grant themselves access to other users' data.
- **NEVER** allow users to bypass the role hierarchy.
- **ALWAYS** validate that the user is authorized to perform the requested
action.
- **ALWAYS** validate that the user is not attempting to escalate their
privileges.
- **ALWAYS** validate that the user is not attempting to access data they do not
have permission to access.
Here's a **bad** example of what **NOT** to do:
```javascript
match /users/{userId} {
// BAD: Allows users to create their own roles because a user can create a new user document with a role of 'admin' and the isAdmin() function will return true
allow create: if (isOwner(userId) && isValidUser(request.resource.data)) || isAdmin();
// BAD: Allows users to update their own roles because a user can update their own user document with a role of 'admin' and the isAdmin() function will return true
allow update: if (isOwner(userId) && isValidUser(request.resource.data)) || isAdmin();
}
```
Here's a **good** example of what **TO** do:
```javascript
match /users/{userId} {
// GOOD: Does NOT allow users to create their own roles unless they are an admin or the user is updating their own role to a less privileged role
allow create: if isAuthenticated() && isValidUser(request.resource.data) && ((isOwner(userId) && request.resource.data.role == 'client') || isAdmin());
// GOOD: Does NOT allow users to update their own roles unless they are an admin
allow update: if isAuthenticated() && isValidUser(request.resource.data) && ((isOwner(userId) && request.resource.data.role == resource.data.role) || isAdmin());
}
```
#### Critical Directives for Secure Generation
- **PREFER USING READ OVER LIST OR GET** `list` and `get` can add complexity to
security rules. Prefer using `read` over them.
- **Date and Timestamp Validation:**
- **Prefer Timestamps:** ALWAYS prefer the `timestamp` type for date fields.
Firestore automatically ensures they are logically valid dates.
- **String Date Risks:** If using strings for dates (e.g., ISO 8601), a regex
check like `isValidDateString` only validates **format**, not **logic** (it
would accept Feb 31st).
- **Regex Escaping:** When using regex for digits, you **MUST** use double
backslashes (e.g., `\\\\d`) in the rules string. Using a single backslash
(`\\d`) is a common bug that causes validation to fail.
- **Immutable Fields:** Fields like `createdAt`, `authorUID`, or any other field
that should not change after creation must be explicitly protected in `update`
rules. (e.g., `request.resource.data.createdAt == resource.data.createdAt`).
**CRITICAL**: When allowing non-owners to update specific fields (like
incrementing a counter), you **MUST** explicitly verify that all other fields
(e.g., `authorName`, `tags`, `body`) remain unchanged to prevent unauthorized
metadata modification. For sensitive fields, ensure that the logged in user is
also the owner of the document.
- **Identity Integrity:** When storing denormalized user identity (e.g.
`authorName`, `authorPhoto`), you **MUST** validate this data.
- **Prefer Auth Token:** If possible, check if
`request.resource.data.authorName == request.auth.token.name`.
- **Strict Validation:** If the auth token is unavailable, you **MUST**
strictly validate the type (string) and length (e.g. < 50 chars) to prevent
spoofing with massive or malicious payloads.
- **Client-Side Fetching:** The most secure pattern is to store ONLY
`authorUid` and fetch the profile client-side. If you denormalize, you
accept the risk of stale or spoofed data unless you validate it.
- **Enforce Strict Schema (No Extraneous Fields):** Documents must not contain
any fields other than those explicitly defined in the data model. This
prevents users from adding arbitrary data.
- **NEVER allow PII EXPOSURE LEAKS:** Never allow PII (Personally Identifiable
Information) to be exposed in the data model. This includes email addresses,
phone numbers, and any other information that could be used to identify a
user. For example, even if a user is logged-in, they should not have access to
read another user's information.
- **No Blanket User Read Access:** You are strictly FORBIDDEN from generating
`allow read: if isAuthenticated();` for the users collection if that
collection is defined to contain email addresses or other private data.
- **CRITICAL: Double-Check Blanket `isAuthenticated` fields:** Ensure that paths
that are protected with only `isAuthenticated()` do not need any additional
checks based on role or any other condition.
- **The "Ownership-Only Update" Trap:** A common critical vulnerability is
allowing updates based solely on ownership (e.g.,
`allow update: if isOwner(resource.data.uid);`). This allows the owner to
corrupt the data schema, delete required fields, or inject malicious payloads.
You **MUST** always combine ownership checks with data validation (e.g.,
`allow update: if isOwner(...) && isValidEntity(...);`) **AND** validate that
self-escalation is not possible.
- **Deep Array Inspection:** It is insufficient to check if a field `is list`.
You **MUST** validate the contents of the array (e.g., ensuring all elements
are strings of a valid UID length) to prevent data corruption or schema
pollution. For example, a `tags` array must verify that every item is a string
AND that each string is within a reasonable length (e.g., < 20 chars).
- **Permission-Field Lockdown:** Fields that control access (e.g., `editors`,
`viewers`, `roles`, `role`, `ownerId`) **MUST** be immutable for non-owner
editors. In `update` rules, use `fieldUnchanged()` for these fields unless the
`request.auth.uid` matches the document's original owner/creator. This
prevents "Permission Escalation" where a collaborator could grant themselves
higher privileges or remove the owner.
### Advanced Validation for Business Logic
Secure rules must enforce the application's business logic. This includes
validating field values against a list of allowed options and controlling how
and when fields can change.
\#### 1. Enforce Enum Values
If a field should only contain specific values (e.g., a status), validate
against a list.
**Example:**
```javascript
// A 'task' document's status can only be one of three values
function isValidStatus() {
let validStatuses = ['pending', 'in-progress', 'completed'];
return request.resource.data.status in validStatuses;
}
allow create: if isValidStatus() && ...
```
\#### 2. Validate State Transitions
For `update` operations, you **MUST** validate that a field is changing from a
valid previous state to a valid new state. This prevents users from bypassing
workflows (e.g., marking a task as 'completed' from 'archived').
**Example:**
```javascript
// A task can only be marked 'completed' if it was 'in-progress'
function validStatusTransition() {
let previousStatus = resource.data.status;
let newStatus = request.resource.data.status;
return (previousStatus == 'in-progress' && newStatus == 'completed') ||
(previousStatus == 'pending' && newStatus == 'in-progress');
}
allow update: if validStatusTransition() && ...
```
#### 3. Strict Path and Relationship Scoping
For any field that references another resource (like an image path or a parent
document ID), you **MUST** ensure it is correctly scoped to the user or valid
within the context.
**Example:**
```javascript
// Ensure image path is within the user's own storage folder
allow create: if isScopedPath(request.resource.data.imageBucket) && ...
```
#### 4. Secure Counter Updates
When allowing users to update a counter (like `voteCount` or `answerCount`), you
**MUST** ensure: 1. **Atomic Increments:** The field is only changing by exactly
+1 or -1. 2. **Isolation:** **NO OTHER FIELDS** are being modified. This is
critical to prevent attackers from hijacking the `authorName` or `content` while
"voting". 3. **Action Verification:** You **MUST** prevent users from
artificially inflating counts. When incrementing a counter, verify that the user
has not already performed the action (e.g., by checking for the existence of a
'like' document) and is not looping updates. * **CRITICAL:** Relying solely on
`!exists(likeDoc)` is insufficient because a malicious user can skip creating
the document and loop the increment. * **SOLUTION:** Use `getAfter()` to verify
that the corresponding tracking document *will exist* after the batch completes.
**Example:**
```javascript
function isValidCounterUpdate(docId) {
// Allow update only if 'voteCount' is the ONLY field changing
return request.resource.data.diff(resource.data).affectedKeys().hasOnly(['voteCount']) &&
// And the change is exactly +1 or -1
math.abs(request.resource.data.voteCount - resource.data.voteCount) == 1 &&
// Verify consistency:
(
// Increment: Vote must NOT exist before, but MUST exist after
(request.resource.data.voteCount > resource.data.voteCount &&
!exists(/databases/$(database)/documents/votes/$(request.auth.uid + '_' + docId)) &&
getAfter(/databases/$(database)/documents/votes/$(request.auth.uid + '_' + docId)) != null) ||
// Decrement: Vote MUST exist before, but must NOT exist after
(request.resource.data.voteCount < resource.data.voteCount &&
exists(/databases/$(database)/documents/votes/$(request.auth.uid + '_' + docId)) &&
getAfter(/databases/$(database)/documents/votes/$(request.auth.uid + '_' + docId)) == null)
);
}
allow update: if isValidCounterUpdate(docId) && ...
```
#### 5. **CRITICAL** Ensure Application Validity
While updating the firestore rules, also ensure that the application still works
after firestore rules updates.
1. **For each collection, implement explicit data validation:**
- Type Checking: 'field is string', 'field is number', 'field is bool', 'field
is timestamp'
- Required fields validation using 'hasRequiredFields()'
- **Enforce Size Limits:** For **EVERY** string, list, and map field, you
**MUST** enforce realistic size limits (e.g., `text.size() < 1000`,
`tags.size() < 20`). **Failure to limit a single string field (like `caption`
or `bio`) allows 1MB attacks, which is a CRITICAL vulnerability.**
- URL validation using 'isValidUrl()' for URL fields
- Email validation using 'isValidEmail()' for email fields
- **Immutable field protection** (authorId, createdAt, etc. should not change on
update)
- **UID protection** using 'uidUnchanged()' on creates and 'uidNotModified()' on
updates should be accompanied with `isDocOwner()`
- **Temporal accuracy** using `isRecent()` for timestamps.
- **Range validation** using `isPositive()` or similar for numbers.
- **Path scoping** using `isScopedPath()` for storage paths.
Structure your rules clearly with comments explaining each rule's purpose.
#### Phase-3: Devil's Advocate Attack
**Critical step:** Systematically attempt to break your own rules using the
following attack vectors. You MUST document the outcome of each attempt.
1. **Public List Exploit:** Can I run a collection query without authentication
and retrieve documents that should be private (e.g., where
`visible == false`)?
1. **Unauthorized Read/Write:** Can I `get`, `create`, `update`, or `delete` a
document that I do not own or have permissions for?
1. **The "Update Bypass":** Can I `create` a valid document and then `update` it
with a 1MB string or invalid fields? (Tests if validation logic is missing
from `update`).
1. **Ownership Hijacking (Create):** Can I create a document and set the
`authorUID` or `ownerId` to another user's ID?
1. **Ownership Hijacking (Update):** Can I `update` an existing document to
change its `authorUID` or `ownerId`?
1. **Immutable Field Modification:** Can I change a `createdAt` or other
immutable timestamp or property on an `update`?
1. **Data Corruption (Type Juggling):** Can I write a `number` to a field that
should be a `string`, or a `string` to a `timestamp`?
1. **Validation Bypass (Create vs. Update):** Can I `create` a valid document
and then `update` it into an invalid state (e.g., remove a required field,
write a string that's too long)?
1. **Resource Exhaustion / DoS:** Can I write an enormous string (e.g., 1MB) to
any field that accepts a string or a massive array to a list field? Every
string field (e.g., `bio`, `url`, `name`) MUST have a `.size()` check. If any
are missing, it's a "Resource Exhaustion/DoS" risk.
1. **Required Field Omission:** Can I `create` or `update` a document while
omitting fields that are marked as required in the data model?
1. **Privilege Escalation:** Can I create an account and assign myself an admin
role by writing `isAdmin: true` to my user profile document? (Tests reliance
on document data vs. custom claims).
1. **Schema Pollution:** Can I `create` or `update` a document and add an
arbitrary, undefined field like `extraData: 'malicious_code'`? (Tests for
strict schema enforcement).
1. **Invalid State Transition:** Can I update a document's `status` field from
`'pending'` directly to `'completed'`, bypassing the required `'in-progress'`
state? (Tests business logic enforcement).
1. **Path Traversal / Scoping Attack:** Can I set a path field (like
`imageBucket` or `profilePic`) to a value that points to another user's data
or a restricted area? (Tests for regex path scoping).
1. **Timestamp Manipulation:** Can I set a `createdAt` field to the past or
future to bypass sorting or logic? (Tests for `request.time` validation).
1. **Negative Value / Overflow:** Can I set a numeric field (like `price` or
`quantity`) to a negative number or an extremely large one? (Tests for range
validation).
1. **The "Mixed Content" Leak:** Create a second user. Can User B read User A's
users document? If "Yes" (because you wanted public profiles), does that
document also contain User A's email or private keys? If both are true, the
rules are insecure.
1. **Counter/Action Replay:** If there is a counter (like `likesCount`), can I
increment it without creating the corresponding tracking document (e.g.,
inside `likes/{userId}`)? Can I increment it twice? (Tests for `getAfter()`
consistency checks).
1. **Orphaned Subcollection Access:** Can I read/write to a subcollection (e.g.,
`users/123/posts/456`) if the parent document (`users/123`) does not exist?
(Tests for parent existence checks).
1. **Query Mismatch:** Do the rules actually allow the queries the app performs?
(e.g., if the app filters by `status == 'published'`, do the rules allow
`list` only when `resource.data.status == 'published'`?)
1. **Validator Pattern Check:** Do **ALL** `update` rules (including owner-only
ones) call the `isValidX()` function? If an `allow update` rule only checks
`isOwner()`, it is a CRITICAL vulnerability.
Document each attack attempt and whether it succeeded. If ANY attack succeeds:
- Fix the security hole
- Regenerate the rules
- **Repeat Phase-3** until no attacks succeed
#### Phase-4: Syntactic Validation
Once devil's advocate testing passes, repeat until rules pass validation.
**After all phases are complete, create or update the `firestore.rules` file.**
### Critical Constraints
1. **Never skip the devil's advocate phase** - this is your primary security
validation
1. **MUST include helper functions** for common operations ('isAuthenticated',
'isOwner', 'uidUnchanged', 'uidNotModified') AND domain validators
('isValidUser', etc.)
1. **MUST document assumed data models** at the beginning of the rules file
1. **Always validate the rules syntax** using 'firebase deploy --only
firestore:rules --dry-run' or a similar tool before outputting the final
file.
1. **Provide complete, runnable code** - no placeholders or TODOs
1. **Document all assumptions** about data structure or access patterns
1. **Always run the devil's advocate attack** after any modification of the
rules.
1. **Determine whether the rules need to be updated** after permission denied
errors occur.
1. **Do not make overly confident guarantees of the security of rules that you
have generated**. It is very difficult to exhaustively guarantee that there
are no vulnerabilities in a rules set, and it is vital to not mislead users
into thinking that their rules are perfect. After an initial rules
generation, you should describe the rules you've written as a solid
prototype, and tell users that before they launch their app to a large
audience, they should work with you to harden and validate the rules file. Be
clear that users should carefully review rules to ensure security.
references/standard/web_sdk_usage.md›
# Firestore Web SDK Usage Guide
This guide focuses on the **Modular Web SDK** (v9+), which is tree-shakeable and
efficient.
## Initialization
```javascript
import { initializeApp } from "firebase/app";
import { getFirestore } from "firebase/firestore";
// If running in Firebase App Hosting, you can skip Firebase Config and instead use:
// const app = initializeApp();
const firebaseConfig = {
// Your config options. Get the values by running 'npx -y firebase-tools@latest apps:sdkconfig <platform> <app-id>'
};
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);
```
## Writing Data
### Set a Document (`setDoc`)
Creates a document if it doesn't exist, or overwrites it if it does.
```javascript
import { doc, setDoc } from "firebase/firestore";
// Create/Overwrite document with ID "LA"
await setDoc(doc(db, "cities", "LA"), {
name: "Los Angeles",
state: "CA",
country: "USA"
});
// To merge with existing data instead of overwriting:
await setDoc(doc(db, "cities", "LA"), { population: 3900000 }, { merge: true });
```
### Add a Document with Auto-ID (`addDoc`)
Use when you don't care about the document ID.
```javascript
import { collection, addDoc } from "firebase/firestore";
const docRef = await addDoc(collection(db, "cities"), {
name: "Tokyo",
country: "Japan"
});
console.log("Document written with ID: ", docRef.id);
```
### Update a Document (`updateDoc`)
Update some fields of an existing document without overwriting the entire
document. Fails if the document doesn't exist.
```javascript
import { doc, updateDoc } from "firebase/firestore";
const laRef = doc(db, "cities", "LA");
await updateDoc(laRef, {
capital: true
});
```
### Transactions
Perform an atomic read-modify-write operation.
```javascript
import { runTransaction, doc } from "firebase/firestore";
const sfDocRef = doc(db, "cities", "SF");
try {
await runTransaction(db, async (transaction) => {
const sfDoc = await transaction.get(sfDocRef);
if (!sfDoc.exists()) {
throw "Document does not exist!";
}
const newPopulation = sfDoc.data().population + 1;
transaction.update(sfDocRef, { population: newPopulation });
});
console.log("Transaction successfully committed!");
} catch (e) {
console.log("Transaction failed: ", e);
}
```
## Reading Data
### Get a Single Document (`getDoc`)
```javascript
import { doc, getDoc } from "firebase/firestore";
const docRef = doc(db, "cities", "SF");
const docSnap = await getDoc(docRef);
if (docSnap.exists()) {
console.log("Document data:", docSnap.data());
} else {
console.log("No such document!");
}
```
### Get Multiple Documents (`getDocs`)
Fetches all documents in a query or collection once.
```javascript
import { collection, getDocs } from "firebase/firestore";
const querySnapshot = await getDocs(collection(db, "cities"));
querySnapshot.forEach((doc) => {
// doc.data() is never undefined for query doc snapshots
console.log(doc.id, " => ", doc.data());
});
```
## Realtime Updates
### Listen to a Document/Query (`onSnapshot`)
```javascript
import { doc, onSnapshot } from "firebase/firestore";
const unsub = onSnapshot(doc(db, "cities", "SF"), (doc) => {
console.log("Current data: ", doc.data());
});
// Stop listening
// unsub();
```
### Handle Changes (Added/Modified/Removed)
```javascript
import { collection, query, where, onSnapshot } from "firebase/firestore";
const q = query(collection(db, "cities"), where("state", "==", "CA"));
const unsubscribe = onSnapshot(q, (snapshot) => {
snapshot.docChanges().forEach((change) => {
if (change.type === "added") {
console.log("New city: ", change.doc.data());
}
if (change.type === "modified") {
console.log("Modified city: ", change.doc.data());
}
if (change.type === "removed") {
console.log("Removed city: ", change.doc.data());
}
});
});
```
## Queries
### Simple and Compound Queries
Use `query()` to combine filters.
```javascript
import { collection, query, where, getDocs } from "firebase/firestore";
const citiesRef = collection(db, "cities");
// Simple equality
const q1 = query(citiesRef, where("state", "==", "CA"));
// Compound (AND)
// Note: Requires an index if filtering on different fields
const q2 = query(citiesRef, where("state", "==", "CA"), where("population", ">", 1000000));
```
### Order and Limit
Sort and limit results.
```javascript
import { orderBy, limit } from "firebase/firestore";
const q = query(citiesRef, orderBy("name"), limit(3));
```
SKILL.md›
---
name: firebase-firestore
description: >-
Sets up, manages, queries, and configures Cloud Firestore databases (Standard/Enterprise edition), including data modeling, security rules, indexes, and SDK integrations (Web, Python, iOS, Android, Flutter). Use when creating/listing Firestore databases, defining data models/indexes, writing SDK queries, or integrating Firestore SDKs. Don't use for Firebase Hosting, Data Connect, Auth, Storage/GCS, Crashlytics, Functions, or BigQuery.
compatibility: This skill is best used with the Firebase CLI, but does not require it. Firebase CLI can be accessed through `npx -y firebase-tools@latest`.
metadata:
category: Databases
---
# Cloud Firestore Database and Operations
Before setting up dependencies, writing data models, or configuring security
rules, you MUST always identify the Firestore instance edition.
## 1. Instance Selection and Edition Detection
Run the following command to list current Firestore databases:
`bash npx -y firebase-tools@latest firestore:databases:list`
### A. Instance Found
1. For each database found, inspect its edition and details:
`bash npx -y firebase-tools@latest firestore:databases:get <database-id>`
1. Ask the user which database instance they wish to target or if they would
prefer to create a new instance.
1. Once the target instance is established:
- If the **`edition`** is `STANDARD`, follow the guides under
`references/standard/`.
- If the **`edition`** is `ENTERPRISE` or native mode, follow the guides
under `references/enterprise/`.
### B. No Instance Found (or New Requested)
If no databases exist or the user requests a new one, default to provisioning an
**Enterprise** edition database and ask the user what location to use. Run
`npx -y firebase-tools@latest firestore:locations` to get the list of options.
Suggest colocating with other resources if applicable.
Once the location is determined, create the database:
`bash npx -y firebase-tools@latest firestore:databases:create <database-id> --edition="enterprise" --location="<selected-location>"`
Proceed with using the guides under `references/enterprise/`.
______________________________________________________________________
## 2. Specialized Guides
Based on the identified or created instance edition, open and read the
corresponding reference guides:
### Standard Edition (`references/standard/`)
- **Provisioning**: Read [provisioning.md](references/standard/provisioning.md)
- **Security Rules**: Read
[security_rules.md](references/standard/security_rules.md)
- **SDK Usage**: Read [web_sdk_usage.md](references/standard/web_sdk_usage.md),
[android_sdk_usage.md](references/standard/android_sdk_usage.md),
[ios_setup.md](references/standard/ios_setup.md), or
[flutter_setup.md](references/standard/flutter_setup.md)
- **Indexes**: Read [indexes.md](references/standard/indexes.md)
### Enterprise Edition / Native Mode (`references/enterprise/`)
- **Provisioning**: Read
[provisioning.md](references/enterprise/provisioning.md)
- **Data Model**: Read [data_model.md](references/enterprise/data_model.md)
- **Security Rules**: Read
[security_rules.md](references/enterprise/security_rules.md)
- **SDK Usage**:
> [!CRITICAL] **Mandatory Reference Reading** Before writing or modifying any
> application code for Firestore Enterprise Edition, you **MUST** read at
> least one of the relevant reference documents below for the target
> platform/language to understand specific architectural requirements and
> pipeline initialization patterns.
Read [web_sdk_usage.md](references/enterprise/web_sdk_usage.md),
[python_sdk_usage.md](references/enterprise/python_sdk_usage.md),
[android_sdk_usage.md](references/enterprise/android_sdk_usage.md),
[ios_setup.md](references/enterprise/ios_setup.md), or
[flutter_setup.md](references/enterprise/flutter_setup.md)
- **Indexes**: Read [indexes.md](references/enterprise/indexes.md)