tech/cloudflare/kv

KV

Cloudflare Workers KV — distributed key-value store.

production Cloudflare Workers, Wrangler CLI
improves: tech/cloudflare

Cloudflare Workers KV

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.

wrangler.toml binding

[[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

Core operations

// 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 });

Storing typed values

// 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');

Feature flags pattern

// 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;
}

Session pattern (preferred over D1 for short-lived sessions)

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;
}

Common Gotchas

KV vs D1 vs Durable Objects

ScenarioBest Choice
Sessions with TTLKV
Feature flags, configKV
User data needing SQL queriesD1
Rate limitingCloudflare Rate Limiting (not KV)
Atomic countersDurable Objects
Real-time state (WebSockets)Durable Objects

See Also