Workers KV
Cloudflare Workers KV namespace for globally distributed key-value storage, including read/write patterns, caching strategies, TTL, list operations, metadata, bulk operations, and the eventual consistency model.
You are an expert in Cloudflare Workers KV, a globally distributed, eventually consistent key-value store optimized for high-read, low-write workloads at the edge. ## Key Points - **Read-after-write in the same location**: Immediately consistent. - **Global propagation**: Eventually consistent, typically within 60 seconds. - **Conflict resolution**: Last-writer-wins. There are no transactions or compare-and-swap operations. - **Caching**: Reads are cached at the edge. Frequently-read keys are served from the nearest cache, making reads extremely fast (sub-millisecond). - **Stale reads after writing**: Expected behavior due to eventual consistency. If you need immediate read-after-write consistency globally, use Durable Objects. - **Key not found after put**: Ensure you are not reading from a different namespace (check preview vs production IDs). - **Value too large**: KV values max out at 25 MiB. For larger objects, use R2. - **List operations slow**: `list()` calls are not cached and can be slow for large namespaces. Use prefixes to narrow results. ## Quick Example ```bash # Create namespace npx wrangler kv namespace create MY_KV # Create a preview namespace for local dev npx wrangler kv namespace create MY_KV --preview ``` ```toml [[kv_namespaces]] binding = "MY_KV" id = "abc123def456" preview_id = "789ghi012jkl" ```
skilldb get cloudflare-workers-skills/Workers KVFull skill: 320 linesWorkers KV — Cloudflare Workers
You are an expert in Cloudflare Workers KV, a globally distributed, eventually consistent key-value store optimized for high-read, low-write workloads at the edge.
Core Philosophy
Overview
KV is designed for read-heavy use cases — configuration, feature flags, static assets, session data, and cached API responses. Writes propagate globally within 60 seconds (usually faster). KV is not suitable for data that requires strong consistency or frequent writes from multiple locations.
Consistency model
- Read-after-write in the same location: Immediately consistent.
- Global propagation: Eventually consistent, typically within 60 seconds.
- Conflict resolution: Last-writer-wins. There are no transactions or compare-and-swap operations.
- Caching: Reads are cached at the edge. Frequently-read keys are served from the nearest cache, making reads extremely fast (sub-millisecond).
Setup
Create a KV namespace
# Create namespace
npx wrangler kv namespace create MY_KV
# Create a preview namespace for local dev
npx wrangler kv namespace create MY_KV --preview
Bind in wrangler.toml
[[kv_namespaces]]
binding = "MY_KV"
id = "abc123def456"
preview_id = "789ghi012jkl"
TypeScript binding
export interface Env {
MY_KV: KVNamespace;
}
Basic Operations
Writing values
// Simple string write
await env.MY_KV.put("user:1234", JSON.stringify({ name: "Alice", role: "admin" }));
// Write with TTL (expires in 1 hour)
await env.MY_KV.put("session:abc", sessionData, { expirationTtl: 3600 });
// Write with absolute expiration (Unix timestamp in seconds)
const oneHourFromNow = Math.floor(Date.now() / 1000) + 3600;
await env.MY_KV.put("token:xyz", tokenData, { expiration: oneHourFromNow });
// Write with metadata (stored alongside the value)
await env.MY_KV.put("page:/about", htmlContent, {
metadata: { contentType: "text/html", version: 3, updatedAt: Date.now() },
});
Reading values
// Read as string (default)
const value = await env.MY_KV.get("user:1234");
if (value === null) {
// Key does not exist
}
// Read as JSON (auto-parsed)
const user = await env.MY_KV.get("user:1234", { type: "json" });
// Read as ArrayBuffer (binary data)
const imageData = await env.MY_KV.get("image:logo", { type: "arrayBuffer" });
// Read as ReadableStream (efficient for large values)
const stream = await env.MY_KV.get("large-file", { type: "stream" });
if (stream) {
return new Response(stream, {
headers: { "content-type": "application/octet-stream" },
});
}
Reading with metadata
// getWithMetadata returns both value and metadata
const result = await env.MY_KV.getWithMetadata("page:/about", { type: "text" });
if (result.value !== null) {
const { value, metadata } = result;
return new Response(value, {
headers: { "content-type": metadata?.contentType || "text/plain" },
});
}
Deleting values
await env.MY_KV.delete("user:1234");
// Delete is idempotent — deleting a non-existent key does not error
List Operations
Listing keys
// List all keys (up to 1000 per call)
const list = await env.MY_KV.list();
// list.keys: Array<{ name: string, expiration?: number, metadata?: unknown }>
// list.list_complete: boolean
// list.cursor: string (for pagination)
// List with prefix filter
const userKeys = await env.MY_KV.list({ prefix: "user:" });
// List with limit
const firstTen = await env.MY_KV.list({ prefix: "user:", limit: 10 });
// Paginate through all keys
async function listAllKeys(kv: KVNamespace, prefix: string): Promise<string[]> {
const keys: string[] = [];
let cursor: string | undefined;
do {
const result = await kv.list({ prefix, cursor });
keys.push(...result.keys.map((k) => k.name));
cursor = result.list_complete ? undefined : result.cursor;
} while (cursor);
return keys;
}
Bulk Operations (via Wrangler CLI)
# Bulk write from a JSON file
# File format: [{"key": "k1", "value": "v1"}, {"key": "k2", "value": "v2"}]
npx wrangler kv bulk put --namespace-id=abc123 ./data.json
# Bulk delete
# File format: ["key1", "key2", "key3"]
npx wrangler kv bulk delete --namespace-id=abc123 ./keys.json
# Read a single key via CLI
npx wrangler kv key get --namespace-id=abc123 "user:1234"
# Write a single key via CLI
npx wrangler kv key put --namespace-id=abc123 "user:1234" '{"name":"Alice"}'
Caching Strategies
Cache-aside pattern
async function getCachedData(env: Env, key: string): Promise<unknown> {
// Try KV first
const cached = await env.MY_KV.get(key, { type: "json" });
if (cached !== null) {
return cached;
}
// Fetch from origin
const fresh = await fetchFromOrigin(key);
// Store in KV with TTL
await env.MY_KV.put(key, JSON.stringify(fresh), { expirationTtl: 300 });
return fresh;
}
Stale-while-revalidate pattern
interface CachedEntry<T> {
data: T;
fetchedAt: number;
}
async function getWithSWR<T>(
env: Env,
ctx: ExecutionContext,
key: string,
fetcher: () => Promise<T>,
maxAgeMs: number = 60_000, // Fresh for 1 minute
staleAgeMs: number = 300_000 // Stale but usable for 5 minutes
): Promise<T> {
const cached = await env.MY_KV.get<CachedEntry<T>>(key, { type: "json" });
const now = Date.now();
if (cached) {
const age = now - cached.fetchedAt;
if (age < maxAgeMs) {
// Fresh — return immediately
return cached.data;
}
if (age < staleAgeMs) {
// Stale — return but revalidate in background
ctx.waitUntil(revalidate(env, key, fetcher));
return cached.data;
}
}
// Expired or missing — fetch synchronously
return revalidate(env, key, fetcher);
}
async function revalidate<T>(
env: Env,
key: string,
fetcher: () => Promise<T>
): Promise<T> {
const data = await fetcher();
const entry: CachedEntry<T> = { data, fetchedAt: Date.now() };
await env.MY_KV.put(key, JSON.stringify(entry), { expirationTtl: 3600 });
return data;
}
Feature flags
interface FeatureFlags {
newCheckout: boolean;
darkMode: boolean;
betaAPI: boolean;
}
async function getFeatureFlags(env: Env): Promise<FeatureFlags> {
const flags = await env.MY_KV.get<FeatureFlags>("config:feature-flags", { type: "json" });
return flags ?? { newCheckout: false, darkMode: false, betaAPI: false };
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const flags = await getFeatureFlags(env);
if (flags.betaAPI) {
return handleBetaAPI(request, env);
}
return handleStableAPI(request, env);
},
};
Key Design Patterns
Key naming conventions
// Use colons as separators for hierarchical keys
"user:1234"
"user:1234:profile"
"user:1234:settings"
"session:abc-def-123"
"cache:api:/v1/products?page=1"
"config:feature-flags"
"page:/blog/my-post"
Storing structured data
// Always serialize to JSON for objects
await env.MY_KV.put("user:1234", JSON.stringify({
id: "1234",
name: "Alice",
email: "alice@example.com",
createdAt: "2024-01-15T00:00:00Z",
}));
// Use metadata for indexing/filtering without reading the full value
await env.MY_KV.put("product:shoe-1", JSON.stringify(largeProductData), {
metadata: { category: "shoes", price: 99.99, inStock: true },
});
Atomic counter alternative (use Durable Objects instead)
KV does not support atomic increments. For counters, rate limiting, or any operation requiring read-modify-write atomicity, use Durable Objects instead. KV's last-writer-wins semantics will lose increments under concurrent writes.
Limits and Quotas
| Resource | Free | Paid |
|---|---|---|
| Reads per day | 100,000 | 10,000,000+ |
| Writes per day | 1,000 | 1,000,000+ |
| Key size | 512 bytes | 512 bytes |
| Value size | 25 MiB | 25 MiB |
| Metadata size | 1024 bytes | 1024 bytes |
| Namespaces per account | 100 | 100 |
Troubleshooting
- Stale reads after writing: Expected behavior due to eventual consistency. If you need immediate read-after-write consistency globally, use Durable Objects.
- Key not found after put: Ensure you are not reading from a different namespace (check preview vs production IDs).
- Value too large: KV values max out at 25 MiB. For larger objects, use R2.
- List operations slow:
list()calls are not cached and can be slow for large namespaces. Use prefixes to narrow results.
Install this skill directly: skilldb add cloudflare-workers-skills
Related Skills
Durable Objects
Cloudflare Durable Objects for stateful edge computing, covering constructor patterns, storage API, WebSocket support, alarm handlers, consistency guarantees, and use cases like rate limiting, collaboration, and game state.
Workers AI
Cloudflare Workers AI for running inference at the edge, covering supported models, text generation, embeddings, image generation, speech-to-text, AI bindings, and streaming responses.
Workers D1
Cloudflare D1 serverless SQLite database for Workers, covering schema management, migrations, queries, prepared statements, batch operations, local development, replication, backups, and performance optimization.
Workers Fundamentals
Cloudflare Workers runtime fundamentals including V8 isolates, wrangler CLI, project setup, local development, deployment, environment variables, secrets, and compatibility dates.
Workers Patterns
Production patterns for Cloudflare Workers including queue consumers, cron triggers, email workers, browser rendering, Hyperdrive database connection pooling, Vectorize vector search, and the analytics engine.
Workers R2
Cloudflare R2 object storage with S3-compatible API, covering bucket operations, multipart uploads, presigned URLs, public buckets, lifecycle rules, event notifications, and cost optimization compared to S3.