Choosing Between Cloudflare KV and R2 for Worker Assets
Choose Cloudflare KV for small edge metadata and R2 for large streamed assets. This guide compares consistency, size limits, and shows a Worker that streams an R2 object.
31 May 2026, 14:27 UTC

Store Worker Assets in KV or R2 Based on Size and Access Pattern
A Cloudflare Worker often needs to read data at the edge, but Cloudflare offers more than one storage target. Choosing the wrong one means paying for unnecessary operations, hitting size limits, or serving stale data. The decision is between Cloudflare KV, a globally distributed key-value store, and Cloudflare R2, an S3-compatible object store with no egress fees.
Useful takeaway: Use KV for small, frequently read items such as configuration flags, feature toggles, or short lookup tables. Use R2 for images, documents, bundles, or any payload larger than a few kilobytes that a Worker streams back to a client.
Decision Constraints
- Item size: KV values have a hard size ceiling; R2 objects can be much larger.
- Consistency: KV is eventually consistent across regions; R2 provides read-after-write consistency for new objects.
- Access pattern: KV suits many small reads; R2 suits streaming whole objects or large uploads.
- Cost model: KV charges for reads and writes; R2 charges for storage and operations but not egress.
KV vs R2 Comparison
| Factor | Cloudflare KV | Cloudflare R2 |
|---|---|---|
| Data model | Key-value pairs | Objects with keys and metadata |
| Typical payload | Small configuration and lookup data | Images, documents, builds, media |
| Consistency | Eventual consistency | Read-after-write for new objects |
| Access style | Get by key | Get, put, and stream object body |
| Egress charges | No egress fee within Workers | No egress fee |
Trade-offs
KV is optimized for many small reads with low latency, but it is not designed for large binaries or for workloads that require immediate consistency after a write. R2 stores larger objects and lets a Worker stream the body directly to the client without buffering the whole payload in memory. The trade-off is that R2 object operations carry more overhead than a simple KV get, so R2 is a poor fit for per-request metadata lookups on a hot path.
Implementation: Stream an R2 Object from a Worker
This example assumes you have Wrangler installed and a Cloudflare API token with Workers and R2 permissions.
1. Create the bucket
Where to run: Local terminal.
Permissions: Authenticated Wrangler session with R2 access.
wrangler r2 bucket create
Replace with your bucket name.
2. Bind the bucket to the Worker
Add this to wrangler.toml in your Worker project:
[[r2_buckets]]
binding = "ASSETS"
bucket_name = "my-assets"
Placeholder: Replace my-assets with the bucket you created.
3. Write the Worker
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
const key = url.pathname.slice(1);
const object = await env.ASSETS.get(key);
if (object === null) {
return new Response("Not found", { status: 404 });
}
return new Response(object.body, {
headers: {
"content-type": object.httpMetadata?.contentType ?? "application/octet-stream",
},
});
},
};
Where to run: In the Worker script, then deploy with wrangler deploy.
Expected check: Upload an object to the bucket, then request https://.workers.dev/. The response should return the object body with a 200 status.
Risk: Avoid object.arrayBuffer() for large objects. Stream object.body directly to stay within Worker memory limits.
4. Store tokens as secrets, not variables
Values in wrangler.toml are plain text. Use secrets for credentials:
wrangler secret put API_TOKEN
Where to run: Local terminal.
Verification and Limitations
Deploy the Worker and open the Cloudflare dashboard under Workers, your service, then Logs. Hit the route and confirm the invocation appears with a successful status.
Limitations: KV is unsuitable for binary assets or immediately consistent workflows. R2 is unsuitable for high-frequency metadata lookups because object operations cost more than KV reads.
Practical check: Upload a test object, request it through the Worker, and confirm the body matches. Overwrite the object with different bytes and request the same key again; R2 should return the new version without waiting for propagation.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.