Firestore Pricing Model
Firestore charges for five main categories of operations:| Category | What’s Charged | Free Tier (Daily) |
|---|---|---|
| Document Reads | Every document returned from a query | 50,000 |
| Document Writes | Every create, update, or delete | 20,000 |
| Document Deletes | Permanent deletions (separate from writes) | 20,000 |
| Storage | Data stored in your database | 1 GB |
| Network Egress | Data transferred out of Google Cloud | 10 GB |
Free Tier: Firestore offers a generous free tier that resets daily. Small applications often stay within these limits. Prices vary by region after exceeding the free tier.
Operation Costs
Understand exactly what you’re charged for with each FirestoreORM operation.Read Operations
| Operation | Cost | Notes |
|---|---|---|
getById('user-123') | 1 read | Single document lookup |
list(100) | 100 reads | Reads up to 100 documents |
query().where('status', '==', 'active').get() | 1 read per result | Charges for every matched document |
query().count() | 1 read per 1000 docs | Aggregation query (much cheaper) |
query().exists() | 1 read | Reads at most 1 document |
findByField('email', 'john@example.com') | 1 read per match | Full collection scan if no index |
getById('user-123', true) with deleted doc | 1 read | Reading soft-deleted docs costs the same |
Write Operations
| Operation | Cost | Notes |
|---|---|---|
create(userData) | 1 write | Single write operation |
bulkCreate([...100 users]) | 100 writes | Batched but still counts as 100 writes |
update('user-123', { name: 'Jane' }) | 1 write | Even if updating one field |
update('user-123', { 'nested.field': 'value' }) | 1 write | Dot notation doesn’t reduce cost |
softDelete('user-123') | 1 write | Updates deletedAt field (not a delete) |
restore('user-123') | 1 write | Sets deletedAt to null |
delete('user-123') | 1 delete | Permanent deletion |
query().update({ status: 'active' }) | 1 read + 1 write per match | Reads documents then updates |
Bulk Operations
Batching Benefits: Firestore batches are atomic and more efficient for network transfer, but you’re still charged for each individual operation. The benefit is reliability and reduced network overhead, not reduced billing.
Real-Time Listeners
- Initial load: 1 read per matching document (e.g., 50 docs = 50 reads)
- Each change: 1 read for the changed document
- Example: 50 initial docs + 100 updates/day = 150 reads/day per listener
What Happens Under the Hood
Simple Query
- ORM automatically adds
.where('deletedAt', '==', null)for soft delete filtering - Firestore executes query with both conditions
- Returns up to 10 matching documents
- Cost: 10 reads (or fewer if less than 10 matches)
Pagination
- If
cursorIdprovided, fetches that document first (1 read) - Executes query starting after cursor document
- Returns up to 20 documents (20 reads)
- Total Cost: 21 reads (20 results + 1 cursor lookup)
Bulk Create
- Validates all 500 documents against Zod schema (no Firestore cost)
- Splits into batches of 500 operations (Firestore batch limit)
- Commits each batch sequentially
- Cost: 500 writes
- Time: ~800ms (varies by network latency)
Query Update
- Executes query to find matching documents (150 reads)
- Batches updates in groups of 500 (single batch in this case)
- Commits all updates (150 writes)
- Total Cost: 150 reads + 150 writes = 300 operations
Query updates are efficient for network traffic (single batch) but you pay for both reads and writes. If you already have the document IDs, use
bulkUpdate() to skip the read cost.Soft Delete
- Fetches document to verify existence (1 read)
- Updates
deletedAtfield to current timestamp (1 write) - Triggers
beforeSoftDeleteandafterSoftDeletehooks - Cost: 1 read + 1 write
Transaction
- Reads both documents within transaction (2 reads)
- Locks both documents until transaction completes
- Validates business logic (no Firestore cost)
- Commits both updates atomically (2 writes)
- Total Cost: 2 reads + 2 writes = 4 operations
Transaction Retries: If a transaction fails due to conflicts (another write to the same documents), Firestore automatically retries up to 5 times. Each retry incurs additional read costs.
Cost Optimization Strategies
1. Use Aggregations Instead of Fetching
- 5,000 active users
- Aggregation: 5 reads
- Fetch all: 5,000 reads
- Savings: 99.9%
2. Always Limit Queries
3. Use exists() for Presence Checks
4. Bulk Operations Over Individual
5. Select Specific Fields
Field selection doesn’t reduce read costs but improves performance by:
- Reducing network bandwidth usage
- Faster JSON parsing
- Lower memory usage
- Helpful in bandwidth-constrained environments
6. Cache Frequently Accessed Data
- Without cache: 10,000 getById calls = 10,000 reads
- With cache (80% hit rate): 2,000 reads + Redis costs
- Savings: 80%
7. Denormalize for Read-Heavy Workloads
Instead of reading multiple collections:Monitoring and Alerting
Track Firestore Usage
Set Budget Alerts
-
Firebase Console:
- Go to Project Settings → Usage and Billing
- Set up budget alerts at 50%, 75%, 90% of your limit
-
Google Cloud Console:
- Create budget alerts with specific thresholds
- Configure email notifications
- Set up programmatic notifications via Pub/Sub
Log Expensive Queries
Cost Estimation Examples
E-commerce Order System
Daily Operations:- 1,000 new orders created: 1,000 writes
- 5,000 users checking order status: 5,000 reads
- 500 order status updates: 500 writes
- 100 order cancellations (soft delete): 100 reads + 100 writes
- Admin dashboard queries (daily stats): ~10,000 reads (aggregations)
- Reads: 15,100
- Writes: 1,600
- Within free tier ✅
Social Media Feed
Daily Operations:- 10,000 users load feed (50 posts each): 500,000 reads
- 5,000 new posts created: 5,000 writes
- 50,000 likes/comments: 50,000 writes
- Real-time feed updates (10,000 users × 100 updates/day): 1,000,000 reads
- Reads: 1,500,000
- Writes: 55,000
- Exceeds free tier ❌
- Cache feed data in Redis (reduce reads by 80%)
- Use pagination with smaller page sizes (50 → 20 posts)
- Replace real-time listeners with polling every 30 seconds
- Denormalize post data to reduce joins
- Reads: ~300,000 (80% cached)
- Writes: 55,000
- Significant cost reduction ✅
Performance Benchmarks
Based on testing with Firebase Admin SDK:| Operation | Documents | Average Time | Cost |
|---|---|---|---|
create() | 1 | ~50ms | 1 write |
bulkCreate() | 100 | ~300ms | 100 writes |
bulkCreate() | 500 | ~800ms | 500 writes |
bulkCreate() | 1,000 | ~1.6s | 1,000 writes |
getById() | 1 | ~30ms | 1 read |
query().get() | 100 | ~100ms | 100 reads |
query().count() | 10,000 | ~200ms | 10 reads |
update() | 1 | ~50ms | 1 write |
bulkUpdate() | 100 | ~350ms | 100 writes |
transaction | 2R + 2W | ~100ms | 2 reads + 2 writes |
Factors Affecting Performance:
- Network latency (varies by region: US, EU, Asia)
- Document size (larger docs take longer to transfer)
- Firestore built-in caching (frequently accessed docs are faster)
- Index warmup (first query using an index may be slower)