# Cloudflare KV Reference Sheet

# 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

```json
{
  "name": "my-worker",
  "kv_namespaces": [
    {
      "binding": "TODO", // Variable name in code
      "id": "06779da6940b431db6e566b4846d64db", // Production namespace
      "preview_id": "06779da6940b431db6e566b484a6a7" // Dev namespace
    }
  ]
}
```

## Development Commands

```bash
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

```javascript
// 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

```javascript
// 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

```javascript
// 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 minute
    
* `300` = 5 minutes
    
* `3600` = 1 hour
    
* `86400` = 24 hours
    
* `604800` = 1 week
    

# Metadata

## Store with Metadata

```javascript
await env.TODO.put("user:123", userData, {
  metadata: {
    version: 1,
    lastUpdated: Date.now(),
    source: "api",
  },
  expirationTtl: 3600,
});
```

## Read with Metadata

```javascript
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

```javascript
// 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

```javascript
"user 123 profile"; // Spaces
"user@123#profile"; // Special characters
"john_profile"; // No structure
"userprofilejohn"; // Hard to read
```

# Listing Keys

## Basic List

```javascript
const result = await env.TODO.list();
// Returns: { keys: [...], truncated: boolean, cursor: string }
```

## List with Prefix

```javascript
const userKeys = await env.TODO.list({ prefix: "user:123:" });
// Only returns keys starting with "user:123:"
```

## Pagination

```javascript
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

```javascript
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

```javascript
// 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

```javascript
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

```javascript
// 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

```javascript
// 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

```javascript
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 (&gt;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
