Firestore
Model, query, and manage data with Google Cloud Firestore NoSQL document database
You are an expert in Google Cloud Firestore for building scalable applications with a flexible NoSQL document database.
## Key Points
- **Using auto-incrementing document IDs** -- Sequential IDs concentrate writes on a single storage partition, creating hot spots. Use Firestore's auto-generated IDs or add random prefixes.
- Document-collection data model with nested subcollections
- Real-time snapshot listeners for live data synchronization
- Strong consistency for all reads
- Multi-region replication and automatic scaling
- Offline support for mobile and web clients
- Integration with Firebase SDKs, Cloud Functions triggers, and Security Rules
- **Design data for your queries.** Firestore does not support arbitrary joins. Denormalize data and use subcollections to match your access patterns.
- **Use composite indexes proactively.** Firestore requires a composite index for queries with multiple filters or mixed ordering. Define them in `firestore.indexes.json`.
- **Prefer batch writes for bulk operations.** A batch can hold up to 500 operations and executes atomically.
- **Use server timestamps.** Always use `FieldValue.serverTimestamp()` instead of client-generated dates for consistency.
- **Limit document size.** A single document is capped at 1 MiB. Store large blobs in Cloud Storage and reference them by URL.
## Quick Example
```javascript
const { Firestore } = require('@google-cloud/firestore');
const db = new Firestore({ projectId: 'my-project' });
```
```python
from google.cloud import firestore
db = firestore.Client(project="my-project")
```skilldb get gcp-services-skills/FirestoreFull skill: 247 linesGCP Service — Firestore
You are an expert in Google Cloud Firestore for building scalable applications with a flexible NoSQL document database.
Core Philosophy
Firestore is a document database designed for application-centric data access. Unlike relational databases where you normalize data and join at query time, Firestore requires you to model data around your queries. If your UI displays a user's profile alongside their recent orders, store them in a way that can be fetched with one or two reads -- not a chain of collection scans. Denormalization is not a compromise in Firestore; it is the correct modeling strategy.
Real-time listeners are Firestore's most powerful feature and should be used deliberately. Snapshot listeners push changes to connected clients instantly, enabling live dashboards, collaborative editing, and chat with no polling. But every active listener holds an open connection and incurs read charges on every matching change. Use listeners for data that genuinely needs to be live and use regular gets for data that does not change during a user session.
Security rules are your server-side validation layer for client-side SDKs. Deploying with allow read, write: if true is equivalent to giving every user admin access to your database. Write granular rules that validate document structure, check authentication state, and enforce ownership constraints. For server-side SDKs (Admin SDK), security rules do not apply -- but that is not an excuse to skip them for client-facing applications.
Anti-Patterns
- Fetching entire collections without filters or pagination -- Reading all documents in a large collection is expensive and slow. Always use WHERE clauses, limit results, and paginate with cursors.
- Designing deeply normalized data that requires multiple reads -- If displaying a single screen requires reading from five collections, the data model is wrong. Denormalize and colocate data that is read together.
- Deploying with open security rules in production -- Rules like
allow read, write: if trueexpose your entire database to any authenticated or unauthenticated user. Define granular per-collection rules. - Using auto-incrementing document IDs -- Sequential IDs concentrate writes on a single storage partition, creating hot spots. Use Firestore's auto-generated IDs or add random prefixes.
- Ignoring composite index requirements -- Queries that combine filters on multiple fields fail at runtime if the required composite index does not exist. Define indexes proactively in
firestore.indexes.json.
Overview
Firestore is a serverless, fully managed NoSQL document database that scales automatically. It stores data in documents organized into collections, supports real-time listeners, offline persistence, and ACID transactions.
Key capabilities:
- Document-collection data model with nested subcollections
- Real-time snapshot listeners for live data synchronization
- Strong consistency for all reads
- Multi-region replication and automatic scaling
- Offline support for mobile and web clients
- Integration with Firebase SDKs, Cloud Functions triggers, and Security Rules
Setup & Configuration
Enable the API and create a database
gcloud services enable firestore.googleapis.com
# Create a Firestore database (Native mode)
gcloud firestore databases create \
--location=us-central1 \
--type=firestore-native
Install client libraries
# Node.js
npm install @google-cloud/firestore
# Python
pip install google-cloud-firestore
# Go
go get cloud.google.com/go/firestore
Initialize the client (Node.js)
const { Firestore } = require('@google-cloud/firestore');
const db = new Firestore({ projectId: 'my-project' });
Initialize the client (Python)
from google.cloud import firestore
db = firestore.Client(project="my-project")
Core Patterns
Create and set documents
// Auto-generated ID
const docRef = await db.collection('users').add({
name: 'Alice',
email: 'alice@example.com',
createdAt: Firestore.FieldValue.serverTimestamp(),
});
// Explicit ID
await db.collection('users').doc('alice-id').set({
name: 'Alice',
email: 'alice@example.com',
});
// Merge (partial update without overwriting)
await db.collection('users').doc('alice-id').set(
{ lastLogin: Firestore.FieldValue.serverTimestamp() },
{ merge: true }
);
Read documents
// Single document
const doc = await db.collection('users').doc('alice-id').get();
if (doc.exists) {
console.log(doc.data());
}
// Query a collection
const snapshot = await db.collection('users')
.where('email', '==', 'alice@example.com')
.limit(10)
.get();
snapshot.forEach(doc => console.log(doc.id, doc.data()));
Compound queries and ordering
# Python: compound query with ordering
docs = (
db.collection("orders")
.where("status", "==", "active")
.where("total", ">=", 100)
.order_by("total", direction=firestore.Query.DESCENDING)
.limit(20)
.stream()
)
for doc in docs:
print(f"{doc.id} => {doc.to_dict()}")
Pagination with cursors
const first = await db.collection('products')
.orderBy('price')
.limit(25)
.get();
const lastDoc = first.docs[first.docs.length - 1];
const next = await db.collection('products')
.orderBy('price')
.startAfter(lastDoc)
.limit(25)
.get();
Transactions
const userRef = db.collection('accounts').doc('user-1');
await db.runTransaction(async (transaction) => {
const doc = await transaction.get(userRef);
const currentBalance = doc.data().balance;
if (currentBalance < 50) {
throw new Error('Insufficient funds');
}
transaction.update(userRef, { balance: currentBalance - 50 });
});
Batch writes
const batch = db.batch();
batch.set(db.collection('users').doc('u1'), { name: 'Alice' });
batch.update(db.collection('users').doc('u2'), { active: true });
batch.delete(db.collection('users').doc('u3'));
await batch.commit();
Real-time listeners (Firebase Web SDK)
import { onSnapshot, collection, query, where } from 'firebase/firestore';
const q = query(
collection(db, 'messages'),
where('roomId', '==', 'room-1')
);
const unsubscribe = onSnapshot(q, (snapshot) => {
snapshot.docChanges().forEach((change) => {
if (change.type === 'added') console.log('New:', change.doc.data());
if (change.type === 'modified') console.log('Updated:', change.doc.data());
if (change.type === 'removed') console.log('Removed:', change.doc.data());
});
});
Composite indexes (firestore.indexes.json)
{
"indexes": [
{
"collectionGroup": "orders",
"queryScope": "COLLECTION",
"fields": [
{ "fieldPath": "status", "order": "ASCENDING" },
{ "fieldPath": "total", "order": "DESCENDING" }
]
}
]
}
Deploy indexes:
gcloud firestore indexes composite create \
--collection-group=orders \
--field-config field-path=status,order=ascending \
--field-config field-path=total,order=descending
Best Practices
- Design data for your queries. Firestore does not support arbitrary joins. Denormalize data and use subcollections to match your access patterns.
- Use composite indexes proactively. Firestore requires a composite index for queries with multiple filters or mixed ordering. Define them in
firestore.indexes.json. - Prefer batch writes for bulk operations. A batch can hold up to 500 operations and executes atomically.
- Use server timestamps. Always use
FieldValue.serverTimestamp()instead of client-generated dates for consistency. - Limit document size. A single document is capped at 1 MiB. Store large blobs in Cloud Storage and reference them by URL.
- Use collection group queries for subcollections. Query across all subcollections with the same name using
collectionGroup().
Common Pitfalls
- Missing composite indexes. Queries that combine filters on multiple fields fail at runtime if the required index does not exist. Check error messages for the index creation link.
- Hot spots from sequential document IDs. Auto-incrementing IDs concentrate writes on a single partition. Use Firestore's auto-generated IDs or randomized prefixes.
- Reading entire collections. Fetching all documents in a large collection is expensive. Always use filters and pagination.
- Exceeding transaction limits. A transaction can read up to 10,000 documents and modify up to 500. Design data to keep transaction scopes small.
- Ignoring security rules in production. Deploying with open rules (
allow read, write: if true) is a security risk. Always define granular rules. - Not handling offline/cache behavior. On mobile clients, Firestore may return cached data. Check
metadata.fromCachewhen freshness matters.
Install this skill directly: skilldb add gcp-services-skills
Related Skills
Bigquery
Analyze large datasets with Google BigQuery serverless data warehouse and SQL engine
Cloud Functions
Build and deploy event-driven serverless functions on Google Cloud Functions
Cloud Run
Deploy and manage containerized applications on Google Cloud Run serverless platform
Cloud Storage
Store, retrieve, and manage objects in Google Cloud Storage buckets
Adversarial Code Review
Adversarial implementation review methodology that validates code completeness against requirements with fresh objectivity. Uses a coach-player dialectical loop to catch real gaps in security, logic, and data flow.
API Design Testing
Design, document, and test APIs following RESTful principles, consistent