Cloudflare D1 free tier limits: what counts as a row written
Cloudflare D1's free tier allows 100,000 rows written and 5 million rows read a day, counted across every database on the account, and resets at 00:00 UTC. When you go over, every query on every D1 database in the accoun
Cloudflare D1's free tier allows 100,000 rows written and 5 million rows read a day, counted across every database on the account, and resets at 00:00 UTC. When you go over, every query on every D1 database in the account fails until the reset. We found that out by taking three products down with one load test. Here is what the limits are, what counts as a row, and how to stay under them.
The limits on Workers Free
| Limit | Workers Free | Workers Paid |
|---|---|---|
| Rows read | 5 million a day | 25 billion a month included |
| Rows written | 100,000 a day | 50 million a month included |
| Storage | 5 GB for the account | 5 GB included, up to 1 TB |
| Database size | 500 MB | 10 GB |
| Databases | 10 | 100,000 included |
| Queries per Worker invocation | 50 | 1,000 |
From Cloudflare's D1 pricing and limits pages, checked on 6 November 2026. Workers Paid is 5 US dollars a month. A db.batch() call counts as one query toward the per-invocation limit, but every row inside it still counts toward reads and writes.
What counts as a row written
This is where the 100,000 goes faster than you expect.
- Every inserted, updated or deleted row counts. Inserting 10 rows is 10.
- Every index the write touches adds a row. A table with two indexes on the columns you insert costs three rows per insert.
- Deletes count. Cleaning up after a test costs as much as the test did, and a retention job that deletes old rows every night spends writes every night.
-
Schema changes (
CREATE,ALTER,DROP) can count as reads and writes.
So one logical event is usually several rows. An email OTP in SignOps is about 8 over its life: the code row and its index, a daily counter, the check, then the deletion when it expires. At 8 rows each, 100,000 writes is about 12,000 codes a day, before anything else on the account writes a row.
What happened to us
We run three products on one Cloudflare account: SignOps, Triplli, quotation software for travel agencies in India, and Cabaro, taxi booking software for cab operators. Each has its own D1 database. On the free plan they share one daily allowance.
On 20 October 2026 we load tested SignOps push on production: 26,000 imported device tokens, three sends to 25,000 devices, then deleting the test devices. Each failed send wrote a row per device, and the cleanup wrote the same again. The account went past 100,000 rows written, and from then until 00:00 UTC, every write failed on all three products, including a travel agency quoting live customers in Triplli. Reads kept working, so pages loaded and every save failed.
The only fix that day was to wait.
How to stay under the limit
Work out the row cost before a test. Rows written times (1 + indexes touched), plus the cleanup. 26,000 devices with two indexes is 78,000 rows to insert and 78,000 to delete: over the daily limit before a single send.
Run volume tests locally. wrangler dev with a local D1 (wrangler d1 migrations apply DB --local) has no daily limit. Keep production tests to a few hundred rows.
Store counts, not events. We replaced one row per failed device with one row per message, platform and reason, holding a count. A 25,000-device send with every token failing now writes a handful of rows instead of 25,000.
Cap per customer, per day. If customers' usage writes rows, give each organisation a daily ceiling well under the account's allowance, so one busy customer cannot use it all.
Give each product its own account. The limit is per account. A second free Cloudflare account gives a second product its own 100,000 writes, and a spike in one cannot reach the other. It costs nothing but a login.
Or pay 5 dollars. Workers Paid includes 50 million rows written a month, about 1.6 million a day, and lifts the daily cutoff within minutes of upgrading.
Checking how close you are
The Cloudflare dashboard shows rows read and written per database under Storage and Databases, D1, Metrics. For the account total, add up every database, or query the GraphQL Analytics API's d1AnalyticsAdaptiveGroups dataset for rowsWritten grouped by day. Check it after any change that adds a table, an index or a scheduled job.
First published on the SignOps blog.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes โ full credit and traffic to the original publisher.