home / skills / itechmeat / llm-code / cloudflare-kv
/skills/cloudflare-kv
npx playbooks add skill itechmeat/llm-code --skill cloudflare-kvReview the files below or copy the command above to add this skill to your agents.
---
name: cloudflare-kv
description: "Cloudflare Workers KV key-value storage playbook: namespaces, bindings, Workers API (get/put/delete/list), metadata, expiration TTL, bulk operations, REST API, consistency model, caching. Keywords: Cloudflare KV, Workers KV, key-value, KVNamespace, binding, metadata, expiration, TTL, cacheTtl, bulk operations, eventually consistent."
---
# Cloudflare Workers KV
KV is a global, low-latency, eventually-consistent key-value store. Optimized for high-read, low-write workloads.
---
## Quick Start
### Create namespace
```bash
npx wrangler kv namespace create MY_KV
```
### Add binding
```jsonc
// wrangler.jsonc
{
"kv_namespaces": [
{
"binding": "MY_KV",
"id": "06779da6940b431db6e566b4846d64db"
}
]
}
```
### Basic Worker
```typescript
export interface Env {
MY_KV: KVNamespace;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
await env.MY_KV.put("user:123", JSON.stringify({ name: "Alice" }));
const user = await env.MY_KV.get("user:123", "json");
const keys = await env.MY_KV.list({ prefix: "user:" });
await env.MY_KV.delete("user:123");
return Response.json({ user, keys: keys.keys });
},
};
```
---
## Binding Configuration
```jsonc
// wrangler.jsonc
{
"kv_namespaces": [
{
"binding": "MY_KV", // Variable name in env
"id": "namespace-uuid", // Namespace ID
"preview_id": "preview-uuid" // Optional: for local dev
}
]
}
```
```toml
# wrangler.toml
[[kv_namespaces]]
binding = "MY_KV"
id = "namespace-uuid"
preview_id = "preview-uuid"
```
### Remote binding (use production in dev)
```jsonc
{
"kv_namespaces": [
{
"binding": "MY_KV",
"id": "namespace-uuid",
"remote": true
}
]
}
```
See [binding.md](references/binding.md) for details.
---
## Workers API
### put(key, value, options?)
```typescript
await env.MY_KV.put("key", "value");
// With options
await env.MY_KV.put("key", "value", {
expirationTtl: 3600, // Expires in 1 hour
metadata: { version: 1 },
});
// With absolute expiration
await env.MY_KV.put("key", "value", {
expiration: Math.floor(Date.now() / 1000) + 86400, // Unix timestamp
});
```
**Value types**: `string | ReadableStream | ArrayBuffer`
**Options**:
| Option | Type | Description |
|--------|------|-------------|
| `expirationTtl` | number | Seconds from now (min 60) |
| `expiration` | number | Unix timestamp (seconds) |
| `metadata` | object | JSON-serializable (max 1024 bytes) |
### get(key, type?)
```typescript
// Text (default)
const text = await env.MY_KV.get("key");
// JSON
const obj = await env.MY_KV.get("key", "json");
// ArrayBuffer
const buffer = await env.MY_KV.get("key", "arrayBuffer");
// Stream
const stream = await env.MY_KV.get("key", "stream");
// With cache TTL
const cached = await env.MY_KV.get("key", { type: "json", cacheTtl: 300 });
```
Returns `null` if key doesn't exist.
### get(keys[]) — Multi-key read
```typescript
const results = await env.MY_KV.get(["key1", "key2", "key3"], "json");
// Returns Map<string, T | null>
for (const [key, value] of results) {
console.log(key, value);
}
```
Maximum 100 keys per call.
### getWithMetadata(key, type?)
```typescript
const { value, metadata } = await env.MY_KV.getWithMetadata("key", "json");
// value: T | null
// metadata: object | null
```
### list(options?)
```typescript
const result = await env.MY_KV.list();
// { keys: [...], list_complete: boolean, cursor?: string }
// With prefix
const users = await env.MY_KV.list({ prefix: "user:" });
// Pagination
let cursor: string | undefined;
do {
const result = await env.MY_KV.list({ cursor, limit: 100 });
for (const key of result.keys) {
console.log(key.name, key.expiration, key.metadata);
}
cursor = result.list_complete ? undefined : result.cursor;
} while (cursor);
```
**Options**:
| Option | Type | Description |
|--------|------|-------------|
| `prefix` | string | Filter keys by prefix |
| `limit` | number | Max keys (default/max: 1000) |
| `cursor` | string | Pagination cursor |
### delete(key)
```typescript
await env.MY_KV.delete("key");
// Resolves even if key doesn't exist
```
See [api.md](references/api.md) for complete reference.
---
## Expiration
### Relative (TTL)
```typescript
await env.MY_KV.put("session:abc", token, {
expirationTtl: 3600, // 1 hour from now
});
```
### Absolute
```typescript
const expires = new Date("2025-01-01").getTime() / 1000;
await env.MY_KV.put("promo:holiday", data, {
expiration: expires,
});
```
**Minimum TTL**: 60 seconds.
Expired keys are automatically deleted and not billed.
---
## Metadata
Store up to 1024 bytes of JSON metadata per key.
```typescript
// Write with metadata
await env.MY_KV.put("file:123", fileContent, {
metadata: {
filename: "document.pdf",
contentType: "application/pdf",
uploadedBy: "user-456",
},
});
// Read with metadata
const { value, metadata } = await env.MY_KV.getWithMetadata("file:123", "stream");
// Metadata in list results
const { keys } = await env.MY_KV.list({ prefix: "file:" });
for (const key of keys) {
console.log(key.name, key.metadata);
}
```
**Pattern**: Store small values directly in metadata:
```typescript
await env.MY_KV.put("config:theme", "", {
metadata: { value: "dark", version: 2 },
});
// Read via list without get()
const { keys } = await env.MY_KV.list({ prefix: "config:" });
const theme = keys.find((k) => k.name === "config:theme")?.metadata?.value;
```
---
## Cache TTL
Control how long reads are cached at edge locations.
```typescript
const data = await env.MY_KV.get("static-config", {
type: "json",
cacheTtl: 3600, // Cache for 1 hour
});
```
**Minimum**: 60 seconds. Default: 60 seconds.
**Use cases**:
- Increase for write-rarely data (static configs)
- Keep low or default for frequently-updated data
---
## Bulk Operations
Bulk operations via Wrangler or REST API only (not Workers binding).
### Wrangler bulk write
```bash
# Create JSON file
cat > data.json << 'EOF'
[
{ "key": "user:1", "value": "{\"name\":\"Alice\"}" },
{ "key": "user:2", "value": "{\"name\":\"Bob\"}", "expiration_ttl": 3600 }
]
EOF
npx wrangler kv bulk put data.json --binding MY_KV
```
### Wrangler bulk delete
```bash
cat > keys.json << 'EOF'
["user:1", "user:2", "user:3"]
EOF
npx wrangler kv bulk delete keys.json --binding MY_KV
```
**Limits**: 10,000 keys per request, 100 MB total.
See [bulk.md](references/bulk.md) for REST API examples.
---
## Wrangler Commands
```bash
# Namespace
wrangler kv namespace create <NAME>
wrangler kv namespace list
wrangler kv namespace delete --namespace-id <ID>
# Key operations
wrangler kv key put <KEY> <VALUE> --binding <BINDING>
wrangler kv key get <KEY> --binding <BINDING>
wrangler kv key delete <KEY> --binding <BINDING>
wrangler kv key list --binding <BINDING> [--prefix <PREFIX>]
# With options
wrangler kv key put <KEY> <VALUE> --binding <BINDING> --ttl 3600
wrangler kv key put <KEY> --binding <BINDING> --path ./file.txt
# Bulk
wrangler kv bulk put <FILE.json> --binding <BINDING>
wrangler kv bulk delete <FILE.json> --binding <BINDING>
```
---
## Consistency Model
KV is **eventually consistent**:
- Writes propagate globally in ~60 seconds (or `cacheTtl`)
- Writes are immediately visible at the origin location
- Concurrent writes to same key: **last write wins**
- Negative lookups (key not found) are cached
### Write rate limit
**1 write per second per key**. Exceeding causes 429 errors.
```typescript
// Implement retry with backoff
async function putWithRetry(key: string, value: string, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
await env.MY_KV.put(key, value);
return;
} catch (e) {
if (i < retries - 1) await sleep(1000 * Math.pow(2, i));
else throw e;
}
}
}
```
### When to use Durable Objects instead
- Need strong consistency
- Need atomic operations
- Need > 1 write/sec to same key
- Need transactions
---
## Limits
| Parameter | Free | Paid |
| --------------------- | ------- | --------- |
| Reads/day | 100,000 | Unlimited |
| Writes/day | 1,000 | Unlimited |
| Storage | 1 GB | Unlimited |
| Namespaces | 1,000 | 1,000 |
| Ops/Worker invocation | 1,000 | 1,000 |
| Parameter | Limit |
| --------------------- | ----------- |
| Key size | 512 bytes |
| Value size | 25 MiB |
| Metadata size | 1,024 bytes |
| Keys per list() | 1,000 |
| Keys per multi-get | 100 |
| Write rate (same key) | 1/sec |
| Minimum TTL | 60 sec |
---
## Pricing
**Free plan** (per day):
- 100,000 reads
- 1,000 writes/deletes/lists
- 1 GB storage
**Paid plan** (per month):
| Metric | Included | Overage |
|--------|----------|---------|
| Reads | 10 million | $0.50/million |
| Writes | 1 million | $5.00/million |
| Deletes | 1 million | $5.00/million |
| Lists | 1 million | $5.00/million |
| Storage | 1 GB | $0.50/GB |
No egress fees. Dashboard/Wrangler queries are billable.
See [pricing.md](references/pricing.md) for optimization tips.
---
## Best Practices
### Use prefixes for organization
```typescript
// Good: prefixed keys
await env.MY_KV.put("user:123:profile", data);
await env.MY_KV.put("user:123:settings", data);
await env.MY_KV.list({ prefix: "user:123:" });
// Bad: flat keys
await env.MY_KV.put("user_123_profile", data);
```
### Store small values in metadata
```typescript
// Avoid list() + get() for each key
await env.MY_KV.put(key, "", { metadata: { value: smallValue } });
```
### Coalesce related data
```typescript
// Instead of many small keys
await env.MY_KV.put(
"user:123:settings",
JSON.stringify({
theme: "dark",
language: "en",
notifications: true,
})
);
```
### Handle missing keys
```typescript
const value = await env.MY_KV.get("key", "json");
if (value === null) {
// Key doesn't exist or expired
}
```
---
## Local Development
```bash
# Uses local KV by default
wrangler dev
# Use remote/production KV
wrangler dev --remote
```
Or set `"remote": true` in binding config.
---
## Prohibitions
- ❌ Do not write to same key more than 1/sec
- ❌ Do not rely on immediate consistency after writes
- ❌ Do not use KV for atomic counters (use Durable Objects)
- ❌ Do not exceed 25 MiB value size
- ❌ Do not use bulk write via Workers binding (use REST API)
---
## References
- [binding.md](references/binding.md) — Binding configuration
- [api.md](references/api.md) — Workers API reference
- [bulk.md](references/bulk.md) — Bulk operations
- [pricing.md](references/pricing.md) — Billing and optimization
## Related Skills
- `cloudflare-workers` — Worker development
- `cloudflare-pages` - Pages Functions with KV
- `cloudflare-durable-objects` - Strong consistency alternative
- `cloudflare-d1` — D1 SQL database operations