Cloudflare Workers KV — distributed key-value store.
KV is a globally distributed, eventually consistent key-value store. Reads are served from the nearest edge (~0ms after cache propagation). Writes propagate globally within ~60 seconds.
Free tier: 100k reads/day, 1k writes/day, 1k deletes/day, 1GB storage. Paid: $0.50/M reads, $5/M writes beyond free.
[[kv_namespaces]]
binding = "KV"
id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# For different namespaces per purpose
[[kv_namespaces]]
binding = "SESSIONS"
id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
[[kv_namespaces]]
binding = "CACHE"
id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
Create a namespace:
wrangler kv namespace create SESSIONS
# → prints the id to put in wrangler.toml
wrangler kv namespace list
// Write (with optional TTL in seconds)
await env.SESSIONS.put(sessionId, JSON.stringify(sessionData), {
expirationTtl: 86400, // 24h — auto-deletes after TTL
});
// Read
const raw = await env.SESSIONS.get(sessionId);
if (!raw) return null;
const session = JSON.parse(raw);
// Read with metadata
const { value, metadata } = await env.SESSIONS.getWithMetadata<{ userId: string }>(key);
// Delete
await env.SESSIONS.delete(sessionId);
// List keys (paginated, max 1000 per call)
const { keys, list_complete, cursor } = await env.KV.list({ prefix: 'session:', limit: 100 });
// JSON values (most common)
await env.KV.put(key, JSON.stringify(value), { expirationTtl: 3600 });
const value = JSON.parse(await env.KV.get(key) ?? 'null');
// ArrayBuffer (binary)
await env.KV.put(key, buffer, { expirationTtl: 3600 });
const buffer = await env.KV.get(key, 'arrayBuffer');
// Stream
const stream = await env.KV.get(key, 'stream');
// Store flags as JSON in KV (update without deploying code)
// Key: "flags:v1", Value: {"new_dashboard": true, "beta_users": ["user1","user2"]}
async function isEnabled(env: Env, flag: string, userId?: string): Promise<boolean> {
const raw = await env.CACHE.get('flags:v1');
if (!raw) return false;
const flags = JSON.parse(raw);
const val = flags[flag];
if (typeof val === 'boolean') return val;
if (Array.isArray(val) && userId) return val.includes(userId);
return false;
}
const SESSION_TTL = 60 * 60 * 24 * 7; // 7 days
async function createSession(env: Env, userId: string): Promise<string> {
const id = crypto.randomUUID();
await env.SESSIONS.put(id, JSON.stringify({ userId, createdAt: Date.now() }), {
expirationTtl: SESSION_TTL,
});
return id;
}
async function getSession(env: Env, id: string) {
const raw = await env.SESSIONS.get(id);
return raw ? JSON.parse(raw) : null;
}
expirationTtl is seconds from now; expiration is a Unix timestamp. Don't mix them.| Scenario | Best Choice |
|---|---|
| Sessions with TTL | KV |
| Feature flags, config | KV |
| User data needing SQL queries | D1 |
| Rate limiting | Cloudflare Rate Limiting (not KV) |
| Atomic counters | Durable Objects |
| Real-time state (WebSockets) | Durable Objects |