Build or update the BlueBubbles external channel plugin for Moltbot (extension package, REST...
npx skills add itechmeat/llm-code --skill "cloudflare-kv"
Install specific skill from multi-skill repository
# 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.
# SKILL.md
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
npx wrangler kv namespace create MY_KV
Add binding
// wrangler.jsonc
{
"kv_namespaces": [
{
"binding": "MY_KV",
"id": "06779da6940b431db6e566b4846d64db"
}
]
}
Basic Worker
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
// wrangler.jsonc
{
"kv_namespaces": [
{
"binding": "MY_KV", // Variable name in env
"id": "namespace-uuid", // Namespace ID
"preview_id": "preview-uuid" // Optional: for local dev
}
]
}
# wrangler.toml
[[kv_namespaces]]
binding = "MY_KV"
id = "namespace-uuid"
preview_id = "preview-uuid"
Remote binding (use production in dev)
{
"kv_namespaces": [
{
"binding": "MY_KV",
"id": "namespace-uuid",
"remote": true
}
]
}
See binding.md for details.
Workers API
put(key, value, options?)
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?)
// 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
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?)
const { value, metadata } = await env.MY_KV.getWithMetadata("key", "json");
// value: T | null
// metadata: object | null
list(options?)
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)
await env.MY_KV.delete("key");
// Resolves even if key doesn't exist
See api.md for complete reference.
Expiration
Relative (TTL)
await env.MY_KV.put("session:abc", token, {
expirationTtl: 3600, // 1 hour from now
});
Absolute
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.
// 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:
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.
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
# 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
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 for REST API examples.
Wrangler Commands
# 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.
// 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 for optimization tips.
Best Practices
Use prefixes for organization
// 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
// Avoid list() + get() for each key
await env.MY_KV.put(key, "", { metadata: { value: smallValue } });
Coalesce related data
// Instead of many small keys
await env.MY_KV.put(
"user:123:settings",
JSON.stringify({
theme: "dark",
language: "en",
notifications: true,
})
);
Handle missing keys
const value = await env.MY_KV.get("key", "json");
if (value === null) {
// Key doesn't exist or expired
}
Local Development
# 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 β Binding configuration
- api.md β Workers API reference
- bulk.md β Bulk operations
- pricing.md β Billing and optimization
Related Skills
cloudflare-workersβ Worker developmentcloudflare-pages- Pages Functions with KVcloudflare-durable-objects- Strong consistency alternativecloudflare-d1β D1 SQL database operations
# Supported AI Coding Agents
This skill is compatible with the SKILL.md standard and works with all major AI coding agents:
Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.