Choosing Stateful Storage in Cloudflare Workers: KV vs Durable Objects for Session Data
Choose Workers KV for fast, read-heavy session storage with eventual consistency. Use Durable Objects when you need linearizable reads/writes and can accept higher latency and cost.
31 May 2026, 00:22 UTC

The Decision: Session Data Storage in Cloudflare Workers
When building applications with Cloudflare Workers, a common architectural decision is how to store session data. The two native options are Workers KV and Durable Objects, which differ fundamentally in consistency model, latency profile, and concurrency behavior. This guide compares the supported options, explains the trade-offs, and shows concrete implementations you can validate yourself.
Constraints and Requirements
Before choosing, identify your application's specific constraints:
- Session size: typically under 1 KiB per user
- Read frequency: every request may need session validation
- Write frequency: updates on login, logout, or state changes
- Consistency needs: must reads always reflect the latest write?
- Concurrency: can multiple simultaneous requests touch the same session?
Comparison Table
| Feature | Workers KV | Durable Objects |
|---|---|---|
| Consistency | Eventual (writes may lag behind reads) | Linearizable (reads see the latest write) |
| Read latency | ~10-30 µs (cached at edge) | ~30-50 µs (plus object routing) |
| Concurrency model | Stateless reads; no coordination between writers | Actor model: one object instance serializes requests |
| Cost profile | Lower per GB stored | Higher per-request cost due to the actor model |
| Code complexity | Lower, but you must handle stale reads | Higher, but consistency is guaranteed |
Trade-off Analysis
When to choose KV: If sessions are read-heavy, writes are infrequent, and you can tolerate brief inconsistency (for example, a user seeing slightly stale data for a short window after an update). KV gives the fastest reads and the simplest code, but your application logic must tolerate the fact that a write may not be immediately visible to subsequent reads.
When to choose Durable Objects: If you need guaranteed consistency — preventing double-spending in a game economy, enforcing a single active login, or maintaining a strictly ordered sequence of updates — Durable Objects are the right fit. Each object processes requests on a single thread, which eliminates race conditions but can become a bottleneck if one session key receives heavy concurrent traffic.
Concrete Implementation: Session Store with KV
Below is a minimal session handler using Workers KV. It assumes a KV namespace bound as SESSION_STORE in your wrangler.toml. Run it with wrangler dev (local) or deploy with wrangler deploy; no special permissions beyond a Workers account are needed.
// worker.js - KV-based session store
export default {
async fetch(request, env) {
const url = new URL(request.url);
const sessionId = url.searchParams.get('session');
if (!sessionId) {
return new Response('No session provided', { status: 400 });
}
// Read session from KV (the 'json' type parses the value for you)
const sessionData = await env.SESSION_STORE.get(sessionId, 'json');
if (!sessionData) {
return new Response('Session not found', { status: 404 });
}
// Update last activity and write back
const updated = { ...sessionData, lastActivity: Date.now() };
await env.SESSION_STORE.put(sessionId, JSON.stringify(updated));
return Response.json(updated);
}
};
Important caveat: the write above is eventually consistent. A read issued immediately after the put, from the same or another worker, may return the old value. To observe this behavior, run a loop locally with wrangler dev that writes and immediately re-reads a counter key; you should not assume the counter increments reliably. If your logic depends on read-after-write correctness, KV is the wrong tool for that path.
Concrete Implementation: Session Store with Durable Objects
With Durable Objects you define a class whose instance owns the session state. Requests for the same session are serialized through one object instance, giving you linearizable updates:
// worker.js - Durable Object session store
export class SessionObject {
constructor(state) {
this.state = state;
}
async fetch(request) {
// Load persisted state (or initialize it)
let session = (await this.state.storage.get('session')) || {
count: 0,
lastActivity: Date.now()
};
session.count++;
session.lastActivity = Date.now();
// Persist before responding so a crash doesn't lose the update
await this.state.storage.put('session', session);
return Response.json(session);
}
}
export default {
async fetch(request, env) {
const sessionId = new URL(request.url).searchParams.get('session');
if (!sessionId) {
return new Response('No session provided', { status: 400 });
}
const id = env.SESSION_OBJECT.idFromName(sessionId);
const stub = env.SESSION_OBJECT.get(id);
return stub.fetch(request);
}
};
This requires a durable_objects binding and a migration entry in wrangler.toml. Because each object serializes its requests, parallel calls for the same sessionId will increment count predictably with no lost updates — that is the property you are paying for.
How to Validate the Choice
- Measure KV latency: under
wrangler devor in production, time a few hundredgetcalls and compare against the expected low-microsecond range for cached reads in your region. - Test DO consistency: fire concurrent requests at the same Durable Object (for example with a script issuing 20 parallel fetches) and confirm the counter ends at exactly the number of requests.
- Confirm quotas and pricing: check the Cloudflare dashboard (Workers → your plan's limits) for current KV and Durable Objects quotas before committing; limits vary by plan and exceeding them causes throttling or errors.
Limitations to Consider
- KV writes are eventually consistent; never use KV for read-after-write critical paths.
- Durable Objects process one request at a time per object instance; hot keys can queue requests and add latency.
- Both services enforce quota limits that vary by account tier; verify current numbers on the dashboard or API before deployment, as they change over time.
Recommendation
For most session storage, start with Workers KV: it is cheaper, faster to read, and simpler. Move to Durable Objects only when you can point to a concrete consistency requirement — a race condition you cannot resolve in application code. Validate with real traffic before migrating, since the cost and latency profiles differ meaningfully.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.