Where do I put state?
Storing data on Cloudflare
Use KV for configuration, R2 for objects, and Postgres or MySQL behind Hyperdrive for records that matter. Keep D1 for prototypes.
Last updated
The products, scored
| Product | My take | Score |
|---|---|---|
| KV | Dead-simple global key-value storage. Design for its consistency model and it's superb. | Score: 9/10 |
| Hyperdrive | Makes an existing database fast from Workers, and does it well. | Score: 8/10 |
| Queues | Fine for background jobs; not yet a serious message bus. | Score: 6/10 |
| R2 | Zero egress changes the math; the reliability hasn't caught up to it yet. | Score: 6/10 |
| D1 | Good for demos and proofs of concept. Not production. | Score: 3/10 |
| Durable Objects | Technically impressive, operationally terrifying. Should have stayed an internal primitive. | Score: 2/10 |
The first question is whether the data is a record, a file, or something you need to distribute. Cloudflare puts all of these products under storage. I would not treat them as interchangeable.
Small values that can be stale
KV is still my default for configuration, feature flags, session data, and cached lookups. It is built for frequent reads and infrequent writes. The trade is eventual consistency. A changed value can take a minute or more to appear everywhere, so do not put transactions or coordination in it.
Files and large objects
R2 is the answer for uploads, media, backups, and other blobs. Its S3 compatibility and zero-egress model are the reasons to choose it. I would still build retries around important operations and keep another copy of anything irreplaceable. The storage economics are stronger than my trust in its operations.
Relational data that matters
For a production system of record, I would use Postgres or MySQL behind Hyperdrive. You keep a conventional database, its mature tooling, and a path away from the platform. Hyperdrive removes most of the connection cost from Workers without pretending the database itself should be an edge primitive.
Cloudflare’s storage guide presents D1 as the lightweight SQL choice for read-heavy applications. I would stop at demos, prototypes, and small disposable datasets. A D1 database is limited to 10 GB and processes queries on a single thread. Read replication helps reads, but it does not change the single-writer design or make manual sharding disappear.
Cloudflare KV vs D1, R2, and Durable Objects
KV vs D1. Use KV when reads are by key and stale values are acceptable. Use D1 when the data needs relational queries and the workload is small enough for its limits. For production SQL, I would move the decision to D1 vs Hyperdrive.
D1 vs Hyperdrive. D1 owns the database and removes almost all setup. Hyperdrive connects Workers to Postgres or MySQL. I prefer Hyperdrive when the data is a system of record or needs mature database tooling.
KV vs Durable Objects. KV distributes read-heavy data globally and accepts eventual consistency. Durable Objects serialize access to one named object and provide strong consistency. That makes Durable Objects a coordination tool, not a stronger replacement for KV.
R2 vs KV. R2 stores files and large blobs. KV stores small values that applications read by key. If the data is an upload, backup, or media asset, use R2.
Coordination is not a database
Durable Objects are useful when one named thing must serialize access, such as a room, counter, or lock. Keep the durable record somewhere else. I would only let a Durable Object hold state that the application can rebuild or afford to lose.
Queues are not storage either. Use them to move background work between systems. At-least-once delivery means consumers must tolerate duplicates, and the business state still belongs in a database.