Cloudflare KV Reference Sheet

Just a guy who loves to write code and watch anime.
Quick Mental Model
KV Namespace = The actual database/storage (has ID like
abc123def456)Binding = Variable name in your code (like
TODO,USERS)Key-Value Store = Simple hash map, not a relational database
Eventually Consistent = Changes take time to propagate globally (up to 60 seconds)
Optimized for reads = Great for caching, not for frequent writes
Configuration
wrangler.jsonc
{
"name": "my-worker",
"kv_namespaces": [
{
"binding": "TODO", // Variable name in code
"id": "06779da6940b431db6e566b4846d64db", // Production namespace
"preview_id": "06779da6940b431db6e566b484a6a7" // Dev namespace
}
]
}
Development Commands
wrangler dev # Local fake KV (starts empty)
wrangler dev --remote # Uses preview_id (real KV, dev data)
wrangler deploy # Uses id (production data)
Core Operations
Basic String Operations
// Write
await env.TODO.put("task:123", "Buy groceries");
// Read
let value = await env.TODO.get("task:123"); // Returns string or null
// Delete
await env.TODO.delete("task:123"); // No error if key doesn't exist
JSON Operations
// Store JSON (manual)
const task = { id: 123, title: "Buy groceries", done: false };
await env.TODO.put("task:123", JSON.stringify(task));
// Read JSON (manual)
let taskJson = await env.TODO.get("task:123");
const task = taskJson ? JSON.parse(taskJson) : null;
// Read JSON (automatic parsing)
const task = await env.TODO.get("task:123", "json"); // Returns object or null
// Update JSON (MUST put back!)
const task = await env.TODO.get("task:123", "json");
if (task) {
task.done = true;
await env.TODO.put("task:123", JSON.stringify(task)); // Required!
}
Expiration
Time-based Expiration
// Expire in X seconds
await env.TODO.put("session:abc", sessionData, {
expirationTtl: 3600, // 1 hour
});
// Expire at specific time
const midnight = new Date();
midnight.setHours(24, 0, 0, 0);
await env.TODO.put("daily-stats", statsData, {
expiration: Math.floor(midnight.getTime() / 1000), // Unix timestamp
});
Common TTL Values
60= 1 minute300= 5 minutes3600= 1 hour86400= 24 hours604800= 1 week
Metadata
Store with Metadata
await env.TODO.put("user:123", userData, {
metadata: {
version: 1,
lastUpdated: Date.now(),
source: "api",
},
expirationTtl: 3600,
});
Read with Metadata
const result = await env.TODO.getWithMetadata("user:123", "json");
if (result.value !== null) {
const user = result.value; // Your data
const meta = result.metadata; // Your metadata object
}
Key Naming Patterns
Good Patterns
// Hierarchical with colons
"user:123:profile";
"user:123:settings";
"user:123:sessions:abc";
// Cache keys
"cache:weather:london:2025-08-09";
"api:github:users:123";
// Feature flags
"feature:new-ui:production";
"feature:beta-test:enabled";
// Rate limiting
"ratelimit:192.168.1.1:2025-08-09-14"; // IP + hour
Avoid These
"user 123 profile"; // Spaces
"user@123#profile"; // Special characters
"john_profile"; // No structure
"userprofilejohn"; // Hard to read
Listing Keys
Basic List
const result = await env.TODO.list();
// Returns: { keys: [...], truncated: boolean, cursor: string }
List with Prefix
const userKeys = await env.TODO.list({ prefix: "user:123:" });
// Only returns keys starting with "user:123:"
Pagination
let allKeys = [];
let cursor = undefined;
do {
const result = await env.TODO.list({
prefix: "user:",
limit: 100,
cursor: cursor,
});
allKeys.push(...result.keys);
cursor = result.cursor;
} while (result.truncated);
⚠️ Warning: list() only returns key names and metadata, NOT values. Still need get() for actual data.
Error Handling
Safe JSON Reading
try {
const data = await env.TODO.get("user:123", "json");
if (data === null) {
return new Response("Not found", { status: 404 });
}
// Use data...
} catch (error) {
if (error.message.includes("JSON")) {
// Corrupted JSON data
await env.TODO.delete("user:123"); // Clean up bad data
return new Response("Data corrupted", { status: 422 });
}
// Network or other error
return new Response("Service unavailable", { status: 503 });
}
Defensive Patterns
// Always provide defaults
const preferences = (await env.TODO.get("user:123:prefs", "json")) || {
theme: "light",
notifications: true,
};
// Safe property access
const theme = (preferences && preferences.theme) || "light";
Common Use Cases
Caching API Responses
const cacheKey = `api:weather:${city}`;
let weather = await env.TODO.get(cacheKey, "json");
if (!weather) {
const response = await fetch(`https://api.weather.com/${city}`);
weather = await response.json();
// Cache for 5 minutes
await env.TODO.put(cacheKey, JSON.stringify(weather), {
expirationTtl: 300,
});
}
return new Response(JSON.stringify(weather));
Session Management
// Create session
await env.TODO.put(
`session:${sessionId}`,
JSON.stringify({
userId: 123,
loginTime: Date.now(),
}),
{ expirationTtl: 3600 }
); // 1 hour
// Check session
const session = await env.TODO.get(`session:${sessionId}`, "json");
if (!session) {
return new Response("Session expired", { status: 401 });
}
Feature Flags
// Set flag
await env.TODO.put("feature:new-ui:enabled", "true");
// Check flag
const enabled = (await env.TODO.get("feature:new-ui:enabled")) === "true";
if (enabled) {
// Show new UI
}
Rate Limiting
const minute = Math.floor(Date.now() / 60000);
const rateLimitKey = `ratelimit:${ip}:${minute}`;
let requests = await env.TODO.get(rateLimitKey);
requests = requests ? parseInt(requests) + 1 : 1;
if (requests > 100) {
return new Response("Rate limited", { status: 429 });
}
await env.TODO.put(rateLimitKey, requests.toString(), {
expirationTtl: 60, // Expire after 1 minute
});
Limits and Gotchas
Size Limits
Key name: 512 bytes max
Value size: 25MB max per value
Metadata: No specific limit (but keep reasonable)
Performance Notes
Reads: Very fast (especially after first access)
Writes: Eventually consistent (up to 60 seconds globally)
List operations: Slow and expensive, avoid in hot paths
Eventually Consistent Behavior
Changes visible immediately at write location
May take 60+ seconds to appear in other regions
Negative lookups (key doesn't exist) are also cached
If you create a key after checking it doesn't exist, other regions might still return
null
When NOT to Use KV
❌ Avoid KV for:
Frequent updates to same key (>10/second)
Transactions or atomic operations
Complex queries or relationships
Real-time consistency requirements
Large datasets you need to iterate through
✅ Perfect for:
Caching API responses
User sessions and preferences
Configuration and feature flags
Static asset serving
Rate limiting counters
Read-heavy workloads






