Firestore Pricing at Scale: What 1M Documents Actually Cost
A worked Firestore pricing model for 1M documents, current read/write/storage rates, and the five patterns behind almost every runaway Firebase bill.
Storing a million documents in Firestore costs roughly $1 to $2 a month. Writing them once costs about 90 cents. Reading all million costs 30 cents. If your Firestore bill is hundreds of dollars, the document count isn't why — your read patterns are.
That gap is the whole story of Firestore pricing, and it's why "how much does Firestore cost at scale?" has no useful answer in gigabytes. Firestore bills per operation, so cost tracks how your app reads, not how much you store. Below are the current numbers, a worked model for a million documents, and the five patterns that generate almost every runaway bill I've been asked to fix.
The current numbers
Firestore Standard edition bills reads, writes, deletes, storage, and egress separately, and the price depends on your database location. Single-region is exactly half the price of multi-region — one of the most consequential decisions you'll make, and it's made once at creation time.
- Document reads — $0.03 per 100,000 (single-region, e.g.
us-central1); $0.06 per 100,000 multi-region (nam5,eur3). - Document writes — $0.09 per 100,000 single-region; $0.18 multi-region.
- Document deletes — $0.01 per 100,000 single-region; $0.02 multi-region.
- Stored data — roughly $0.15–$0.18 per GiB per month, *including* index and metadata overhead.
- Free tier, per day — 50,000 reads, 20,000 writes, 20,000 deletes, 1 GiB stored, 10 GiB egress per month. Note this applies to the default database only; named databases get no free quota.
Prices shift and vary by region, so treat these as the shape of the model rather than a quote — check Google's current Firestore pricing page for your location before budgeting.
What a million documents actually costs
Take a million documents averaging 1 KiB each. Raw data is about 1 GiB, but Firestore charges for indexes and metadata too, so budget roughly 2–3 GiB in practice — single-field indexes on every field are created by default, and they add up fast.
- Load them in (1M writes): $0.90 single-region, $1.80 multi-region — one-off.
- Store them (~3 GiB with index overhead): about $0.45–$0.55 per month.
- Read every one of them once: $0.30 single-region, $0.60 multi-region.
- Delete the lot: $0.10 single-region, $0.20 multi-region.
- Running total to hold a million documents for a month: well under $2.
This is why the "Firestore is expensive" reputation confuses people who've only ever stored data in it. A full scan of your entire database costs less than a coffee. The bill arrives when something does that scan two hundred times a day.
Firestore doesn't charge you for having data. It charges you for touching it — and the default patterns touch it far more than you'd guess.
The five patterns that generate the bill
In every Firestore cost audit I've done, the overwhelming majority of spend traces to a handful of specific behaviours. They're all fixable, and none of them require a rewrite.
1. Listeners that re-read everything on reconnect
Realtime listeners are billed a read per document added or changed — cheap while connected. The expensive part is reconnection. If offline persistence is disabled, every disconnect-and-reconnect bills you as though you'd issued a brand-new query. With persistence enabled, the same happens after a 30-minute disconnect. A dashboard listening to 200 documents, open for 5,000 users who reconnect 20 times a day, is 20 million reads a day — around $180 a month single-region, double that on multi-region, for one screen.
2. Using offset() instead of cursors
Firestore charges a read for every document an offset skips. Page 26 of a 20-per-page list bills 520 reads to return 20 documents, and the cost grows the deeper users browse. Cursors cost the same on page 500 as on page 1.
// WRONG: offset bills you a read for every skipped document.
// Page 26 of a 20-per-page list = 520 reads to return 20 documents.
const page = await db.collection("orders")
.orderBy("createdAt", "desc")
.offset(500)
.limit(20)
.get();
// RIGHT: cursors bill you only for what you return — 20 reads, on any page.
const page = await db.collection("orders")
.orderBy("createdAt", "desc")
.startAfter(lastVisibleDoc) // the DocumentSnapshot from the previous page
.limit(20)
.get();3. Calling count() on a hot path
Aggregation queries aren't free. count(), sum(), and avg() bill one read per batch of up to 1,000 index entries scanned, with a minimum of one read. So counting a million matching documents costs about 1,000 reads. Poll that counter every 30 seconds and you're at 2.88 million reads a day — roughly $26 a month single-region, $52 multi-region, to display one number. Precompute it instead.
// A counter you can read for 1 document read instead of ~1,000.
// Update it in the same transaction as the write it counts.
await db.runTransaction(async (tx) => {
const statsRef = db.doc(`tenants/${tenantId}/stats/orders`);
tx.set(newOrderRef, orderData);
tx.set(statsRef, {
total: FieldValue.increment(1),
updatedAt: FieldValue.serverTimestamp(),
}, { merge: true });
});
// Hot counters (>1 write/sec sustained) need sharding —
// write to one of N shard docs at random, sum the shards on read.4. Multi-range queries that bill index-entry reads
Queries with up to one range filter are exempt from index-entry read charges. Add a second range filter and you start paying one read per 1,000 index entries scanned — on top of the documents returned. Watch for the traps: a field in orderBy counts as a range field once another range filter exists, and __name__ always counts as one, even in an equality filter. Use Query Explain to see what a query really costs before it ships.
5. get() and exists() inside security rules
Security rules that look up another document to authorise a request bill you for those reads. A typical multi-tenant rule checking membership adds a read to every single request. You're charged once per dependent document per request, but rules are re-evaluated when a listener's results update, when a device reconnects, and whenever you change the rules — so on realtime surfaces the multiplier is much larger than it looks. Denormalise the tenant ID onto the document and check it directly where you can.
Three more things that quietly cost money
- Every query has a minimum charge of one read, even when it returns nothing. Polling loops that usually find no results still cost.
- Non-document operations bill a read too — listing collection IDs, for example, and once per request if you paginate.
- Egress is only free to 10 GiB a month, then roughly $0.12 per GiB to the internet. Serving large documents directly to mobile clients adds up separately from read costs.
How to cut a Firestore bill, in priority order
Start by finding out where reads come from rather than guessing — the usage breakdown in the Firebase console plus Query Explain on your busiest screens will usually identify the top two sources in an afternoon.
- Instrument first. Identify your three highest-read screens. Optimising anything else is wasted effort.
- Replace offsets with cursors. Mechanical change, immediate saving, near-zero risk.
- Precompute counts and aggregates into a stats document updated transactionally, sharded if the write rate is high.
- Fix listener scope. Enable offline persistence, tighten queries with
limit(), and detach listeners when a screen isn't visible. - Audit security rules for
get()andexists()calls on hot paths, and denormalise the fields those lookups need. - Delete unused indexes. Single-field indexes are created by default on every field and you pay to store all of them; exempt the ones you never query.
- Re-examine multi-region. If you don't have a genuine multi-region availability requirement, single-region halves every operation price. This one can't be changed later without a migration.
When Firestore stops being the right database
Cost optimisation has a floor. If you're routinely fighting the query model — needing joins, several range filters, transactional reporting, or ad-hoc analytics — you're paying a per-operation tax to emulate a relational database, and no amount of denormalising fixes that. That's the point to look at Postgres.
It isn't a weekend job. I migrate teams incrementally: dual-write to both stores, backfill history, verify the two agree, then move reads across one surface at a time. If that's where you're heading, the Supabase developer page covers where most teams land, and Next.js SaaS development covers the application side.
Common questions
Does document size affect Firestore read costs?
Not the read charge itself — Standard edition bills per document, so a 2 KiB document and a 200-byte document cost the same to read. Size affects storage and egress instead. It does matter if you're on Enterprise edition, which bills per unit rather than per document, and it always matters for mobile bandwidth.
Is Firestore cheaper than Postgres at scale?
For spiky, low-traffic, or highly variable workloads Firestore often wins, because you pay nothing when idle and there's no instance to size. For steady high-traffic read workloads a right-sized Postgres instance is usually cheaper and far more predictable, since you're paying for capacity rather than per operation. The crossover depends entirely on your read volume, not your data volume.
Can I set a hard spending cap on Firestore?
Not a true hard cap — Google Cloud budgets send alerts but don't stop requests, and your app keeps serving (and billing) past the threshold. Teams who need a hard stop usually wire a budget alert to a Pub/Sub topic and a Cloud Function that disables billing on the project, which takes the application down. Budget alerts plus read instrumentation are the realistic answer for most people.
Where I come into this
I built Database Vault, a SaaS that runs scheduled backups across PostgreSQL, MySQL, MongoDB, Firebase, and Supabase. Backing up a Firestore database means reading every document in it, on a schedule, for many customers — which is a fairly direct education in what reads cost and how to page through large collections without setting money on fire.
If your bill is growing faster than your users, I do this as a Firebase developer: find the reads, cut them, and tell you honestly whether the fix is a query change or a different database. Send me your usage breakdown and your busiest screen — details here — and you'll get a straight assessment of where the money is going.