Cloudflare D1 pricing

How does Cloudflare D1 pricing work?

Cloudflare D1 bills on three core metrics: rows read, rows written, and storage above your plan limits. It scales to zero, so you don't pay for database hours, capacity units, or idle compute when your application isn't actively querying the database.

On the Workers Free plan, you get 5 million rows read per day, 100,000 rows written per day, and 5 GB of total storage across up to 10 databases. On the Workers Paid plan, you get 25 billion rows read and 50 million rows written each month included, after which reads cost $0.001 per million rows and writes cost $1.00 per million rows.

The three billable metrics in D1

D1 strips away the concept of server provisioning. If nobody talks to your database at 3:00 AM, you're not paying for a standby instance.

Queries you run against D1 will count as rows read, rows written, or both for transactions or batches. There are no extra charges for data transfer (egress) or throughput (bandwidth) when accessing data from D1, and read replication doesn't add any extra surcharge either. You just pay for the queries executed against those replicas.

While D1 itself doesn't charge for database compute, remember that your Workers compute is billed separately. If your Worker takes a D1 query result and runs expensive transformations or serializes massive JSON payloads, that CPU time gets billed under standard Workers pricing.

DDL operations like CREATE, ALTER, and DROP modify database structure, and they can contribute a mix of read rows and write rows depending on what they touch.

Free plan vs Workers Paid plan limits

The difference between the Free and Paid plans isn't just about rates; it also determines what happens when your traffic spikes.

Free daily usage limits reset at 00:00 UTC every day. On the Workers Paid plan, your monthly included usage resets on your monthly subscription renewal date, which lines up with the day you first subscribed.

Metric or limitWorkers Free planWorkers Paid plan
Included rows read5 million / day25 billion / month included
Additional rows readN/A (queries blocked)$0.001 / million rows
Included rows written100,000 / day50 million / month included
Additional rows writtenN/A (queries blocked)$1.00 / million rows
Included storage5 GB (total)5 GB included
Additional storageN/A (writes blocked)$0.75 / GB-mo
Database limit10 databases100,000 included ($0.10/mo per extra db, up to 1,000,000)
Maximum database size500 MB10 GB
Maximum storage per account5 GB1 TB (can be increased by request)
Queries per Worker invocation501,000
Time Travel point-in-time recovery7 days30 days

If you hit your daily read or write limits on the Workers Free plan, the D1 API blocks further queries and returns errors to your client. If you hit your 5 GB storage limit on the Free plan, you can't insert new data, create or alter tables, or create indexes and triggers until you delete unused databases or clean up stale data. Upgrading to the Workers Paid plan removes those limits, typically within minutes.

On the Paid plan, keep an eye on database counts if you create databases per tenant. The first 100,000 databases are included, but each additional database costs $0.10 per month even if it sits empty or receives zero traffic. An empty database consumes about 12 KB of storage on its own.

What counts as a row read

The easiest way to blow your budget on D1 is misunderstanding what counts as a row read. Rows read measure how many rows a query reads or scans, not how many rows it hands back to your code.

If you have a table with 5,000 rows and run a full table scan like this:

SELECT * FROM users;

That single query counts as 5,000 rows read.

Row size and column count don't change this math at all. A row that takes up 1 KB and a row that takes up 100 KB both count as one row. (The maximum row size in D1 is 2,000,000 bytes, or 2 MB.)

The trap comes with filtering on unindexed columns. Suppose your table holds 100,000 user records and you run:

SELECT id, email FROM users WHERE signup_source = 'newsletter';

If signup_source isn't indexed, the SQLite engine has to inspect every single record in the table to figure out which ones match. Even if only three users signed up from the newsletter and your Worker gets an array of three items, D1 counts that as 100,000 rows read. Run that query fifty times a day on the Free plan, and you've burned your entire 5 million daily read allowance on a query that returned 150 rows in total.

Queries executed from the Cloudflare dashboard or using the Wrangler CLI count as billable reads or writes too. Running a quick ad-hoc table inspection from the dashboard runs a full table scan against production data.

Writing data and the index tax

Rows written measure the number of rows written to the database. Write operations include standard INSERT, UPDATE, and DELETE statements.

Defining indexes on your tables is essential for cutting rows read, but indexes introduce a trade-off on write volume. Writing to an indexed column adds an additional written row because D1 writes one row to the table itself and one row to the index. If you have a table with three separate indexes and insert a new row that touches all three, that single insert produces multiple written rows.

Both tables and indexes count toward the total storage consumed across your account. An empty table consumes at least a few kilobytes depending on the number of columns, and storage is billed based on the sum of all databases in your account.

Technical limits to keep in mind

D1 has hard boundaries that shape how you build your application:

  • Single-threaded execution: Each individual D1 database is backed by a single Durable Object and processes queries one at a time.
  • Worker bindings and connections: A single Worker can bind up to approximately 5,000 D1 databases (within the 1 MB script metadata limit), and open up to six simultaneous connections to D1 per invocation.
  • Batching: Use batch() to send multiple SQL statements in a single request so you pay the per-request overhead once for the entire batch.
  • Query size and duration limits: Maximum SQL statement length is 100,000 bytes (100 KB), bound parameters are capped at 100 per query, and SQL query duration is capped at 30 seconds (which also applies to an entire batch() call). Effective 2026-11-04, the maximum query result size is 50 MB, and queries returning more will fail with an error (though REST API imports and exports are exempt).
  • Table limits: Maximum of 100 columns per table and unlimited rows (up to the database size cap). File imports via d1 execute can be up to 5 GB.
  • Time Travel: Restores are capped at 10 operations per 10 minutes per database.

How to track and cut D1 costs

Every query returns a meta object that makes debugging usage straightforward. You don't have to guess what a query cost:

{
  "meta": {
    "duration": 1.2,
    "rows_read": 1000,
    "rows_written": 1,
    "size_after": 24576
  }
}

Check the rows_read and rows_written properties in your logs during development. You can also view aggregate usage in the Cloudflare dashboard or pull it via the GraphQL Analytics API.

To keep rows read down:

  1. Index every column you filter or join on. This turns 50,000-row table scans into direct point lookups that read a few rows.
  2. Avoid gratuitous SELECT * scans. If you only need a row count or an ID, make the query specific.
  3. Be deliberate with indexes on high-write tables. Don't index columns you rarely query, because every write to an indexed column costs an extra row written.

Catching runaway database spend with Costbrake

If an unindexed query slips into production or a cron job starts looping, your read volume can run wild before you notice. Costbrake watches what you spend across Cloudflare and 22 other cloud, AI and SaaS providers, running hourly checks on its Free plan, every 5 minutes on Pro and Team, and every minute during a spike.

For Cloudflare, Costbrake reads usage priced at list rates along with billed daily dollars about a day later. If an hour costs more than 3x your 7-day baseline and at least $5 more than usual, it fires a spike alert to email, Slack, Discord, webhooks, or PagerDuty. You can also set forecast alerts when month-to-date spend plus your 72-hour pace projects an overrun.

Costbrake's paid plans also offer opt-in, reversible brakes for Cloudflare, allowing you to pause cron triggers, pause queue consumers, or detach custom domains with an admin confirmation to stop runaway traffic. Setup takes a couple of minutes; you can connect by hand in the app or tell your coding agent to handle it via agent setup.

Questions and answers

Is Cloudflare D1 free?

Yes. On the Workers Free plan, D1 includes 5 million rows read per day, 100,000 rows written per day, and 5 GB of total storage across up to 10 databases. If you exceed those limits, queries are blocked until the daily reset at 00:00 UTC.

What counts as a row read in D1?

Rows read measure the total number of rows scanned by the query engine to satisfy your request, regardless of row size. If a query filters on an unindexed column across a table of 10,000 rows, it counts as 10,000 rows read even if the query returns only a single row to your Worker.

How do I reduce D1 rows read?

Add indexes to columns used in your WHERE clauses and join conditions so the database engine can jump directly to the target rows rather than performing full table scans. You can check the rows_read field in the query's returned meta object to confirm how many rows were scanned during execution.

Does Cloudflare D1 charge for data transfer or egress?

No, there are no data transfer (egress) or throughput (bandwidth) charges for data accessed from D1. You only pay for rows read, rows written, and storage beyond the included limits.

Why did adding an index increase my rows written?

Writing to an indexed column adds an additional written row because D1 writes one row to the underlying table and one row to the index structure. If you update or insert values into indexed columns, expect your rows_written count to reflect both writes.

Sources

Updated