jackspace/claudeskillz/cloudflare-kv

Trust tier 3
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

Complete knowledge domain for Cloudflare Workers KV - global, low-latency key-value storage on Cloudflare's edge network. Use when: creating KV namespaces, storing configuration data, caching API responses, managing user preferences, implementing TTL expiration, handling KV metadata, or encountering "KV_ERROR", "429 too many requests", "kv rate limit", cacheTtl errors, or eventual consistency issues. Keywords: kv storage, cloudflare kv, workers kv, kv namespace, kv bindings, kv cache, kv ttl, kv metadata, kv list, kv pagination, cache optimization, edge caching, KV_ERROR, 429 too many requests, kv rate limit, eventually consistent, wrangler kv, kv operations, key value storage

Install

npx vskill@latest install jackspace/claudeskillz/cloudflare-kv

SKILL.md

Cloudflare Workers KV

Status: Production Ready ✅ Last Updated: 2025-10-21 Dependencies: cloudflare-worker-base (for Worker setup) Latest Versions: wrangler@4.43.0, @cloudflare/workers-types@4.20251014.0


Quick Start (5 Minutes)

1. Create KV Namespace
bash
# Create a new KV namespace
npx wrangler kv namespace create MY_NAMESPACE
# Output includes namespace_id - save this!
# ✅ Success!
# Add the following to your wrangler.toml or wrangler.jsonc:
#
# [[kv_namespaces]]
# binding = "MY_NAMESPACE"
# id = "<UUID>"

For development (preview) namespace:

bash
npx wrangler kv namespace create MY_NAMESPACE --preview
# Output:
# [[kv_namespaces]]
# binding = "MY_NAMESPACE"
# preview_id = "<UUID>"
2. Configure Bindings

Add to your wrangler.jsonc:

jsonc
{
"name": "my-worker",
"main": "src/index.ts",
"compatibility_date": "2025-10-11",
"kv_namespaces": [
{
"binding": "MY_NAMESPACE", // Available as env.MY_NAMESPACE
"id": "<production-uuid>", // Production namespace ID
"preview_id": "<preview-uuid>" // Local dev namespace ID (optional)
}
]
}

Or use `wrangler.toml`:

toml
name = "my-worker"
main = "src/index.ts"
compatibility_date = "2025-10-11"
[[kv_namespaces]]
binding = "MY_NAMESPACE"
id = "<production-uuid>"
preview_id = "<preview-uuid>" # optional

CRITICAL:

  • binding is how you access the namespace in code (env.MY_NAMESPACE)
  • id is the production namespace UUID
  • preview_id is for local dev (optional, separate namespace)
  • Never commit real namespace IDs to public repos - use environment variables or secrets
3. Write Your First Key-Value Pair
typescript
import { Hono } from 'hono';
type Bindings = {
MY_NAMESPACE: KVNamespace;
};
const app = new Hono<{ Bindings: Bindings }>();
app.post('/set/:key', async (c) => {
const key = c.req.param('key');
const value = await c.req.text();
// Simple write
await c.env.MY_NAMESPACE.put(key, value);
return c.json({ success: true, key });
});
app.get('/get/:key', async (c) => {
const key = c.req.param('key');
const value = await c.env.MY_NAMESPACE.get(key);
if (!value) {
return c.json({ error: 'Not found' }, 404);
}
return c.json({ value });
});
export default app;
4. Test Locally
bash
# Start local development server
npm run dev
# In another terminal, test the endpoints
curl -X POST http://localhost:8787/set/test -d "Hello KV"
# {"success":true,"key":"test"}
curl http://localhost:8787/get/test
# {"value":"Hello KV"}

Complete Workers KV API

1. Read Operations
get() - Read Single Key
typescript
// Get as string (default)
const value: string | null = await env.MY_KV.get('my-key');
// Get as JSON
const data: MyType | null = await env.MY_KV.get('my-key', { type: 'json' });
// Get as ArrayBuffer
const buffer: ArrayBuffer | null = await env.MY_KV.get('my-key', { type: 'arrayBuffer' });
// Get as ReadableStream
const stream: ReadableStream | null = await env.MY_KV.get('my-key', { type: 'stream' });
// Get with cache optimization
const value = await env.MY_KV.get('my-key', {
type: 'text',
cacheTtl: 300, // Cache at edge for 5 minutes (minimum 60 seconds)
});
get() - Read Multiple Keys (Bulk)
typescript
// Read multiple keys at once (counts as 1 operation)
const keys = ['key1', 'key2', 'key3'];
const values: Map<string, string | null> = await env.MY_KV.get(keys);
// Access values
const value1 = values.get('key1'); // string | null
const value2 = values.get('key2'); // string | null
// Convert to object
const obj = Object.fromEntries(values);
getWithMetadata() - Read with Metadata
typescript
// Get single key with metadata
const { value, metadata } = await env.MY_KV.getWithMetadata('my-key');
// value: string | null
// metadata: any | null
// Get as JSON with metadata
const { value, metadata } = await env.MY_KV.getWithMetadata<MyType>('my-key', {
type: 'json',
cacheTtl: 300,
});
// Get multiple keys with metadata
const keys = ['key1', 'key2'];
const result: Map<string, { value: string | null, metadata: any | null }> =
await env.MY_KV.getWithMetadata(keys);
for (const [key, data] of result) {
console.log(key, data.value, data.metadata);
}

Type Options:

  • text (default) - Returns string
  • json - Parses JSON, returns object
  • arrayBuffer - Returns ArrayBuffer
  • stream - Returns ReadableStream

Note: Bulk read with get(keys[]) only supports text and json types. For arrayBuffer or stream, use individual get() calls with Promise.all().


2. Write Operations
put() - Write Key-Value Pair
typescript
// Simple write
await env.MY_KV.put('key', 'value');
// Write JSON
await env.MY_KV.put('user:123', JSON.stringify({ name: 'John', age: 30 }));
// Write with expiration (TTL)
await env.MY_KV.put('session:abc', sessionData, {
expirationTtl: 3600, // Expire in 1 hour (minimum 60 seconds)
});
// Write with absolute expiration
const expirationTime = Math.floor(Date.now() / 1000) + 86400; // 24 hours from now
await env.MY_KV.put('token', tokenValue, {
expiration: expirationTime, // Seconds since epoch
});
// Write with metadata
await env.MY_KV.put('config:theme', 'dark', {
metadata: {
updatedAt: Date.now(),
updatedBy: 'admin',
version: 2
},
});
// Write with everything
await env.MY_KV.put('feature:flags', JSON.stringify(flags), {
expirationTtl: 600,
metadata: { source: 'api', timestamp: Date.now() },
});

CRITICAL Limits:

  • Key size: Maximum 512 bytes
  • Value size: Maximum 25 MiB
  • Metadata size: Maximum 1024 bytes (JSON serialized)
  • Write rate: Maximum 1 write per second per key
  • Expiration minimum: 60 seconds (both TTL and absolute)

Rate Limit Handling:

typescript
async function putWithRetry(
kv: KVNamespace,
key: string,
value: string,
options?: KVPutOptions
) {
let attempts = 0;
const maxAttempts = 5;
let delay = 1000; // Start with 1 second
while (attempts < maxAttempts) {
try {
await kv.put(key, value, options);
return; // Success
} catch (error) {
const message = (error as Error).message;
if (message.includes('429') || message.includes('Too Many Requests')) {
attempts++;
if (attempts >= maxAttempts) {
throw new Error('Max retry attempts reached');
}
console.warn(`Attempt ${attempts} failed. Retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
// Exponential backoff
delay *= 2;
} else {
throw error; // Different error, rethrow
}
}
}
}

3. List Operations
list() - List Keys
typescript
// List all keys (up to 1000)
const result = await env.MY_KV.list();
console.log(result.keys); // Array of key objects
console.log(result.list_complete); // boolean - false if more keys exist
console.log(result.cursor); // string - for pagination
// List with prefix filter
const result = await env.MY_KV.list({
prefix: 'user:', // Only keys starting with 'user:'
});
// List with limit
const result = await env.MY_KV.list({
limit: 100, // Maximum 1000 (default 1000)
});
// Pagination with cursor
let cursor: string | undefined;
let allKeys: any[] = [];
do {
const result = await env.MY_KV.list({ cursor });
allKeys = allKeys.concat(result.keys);
cursor = result.list_complete ? undefined : result.cursor;
} while (cursor);
// Combined: prefix + pagination
let cursor: string | undefined;
const userKeys: any[] = [];
do {
const result = await env.MY_KV.list({
prefix: 'user:',
cursor,
});
userKeys.push(...result.keys);
cursor = result.list_complete ? undefined : result.cursor;
} while (cursor);

List Response Format:

typescript
{
keys: [
{
name: "user:123",
expiration: 1234567890, // Optional: seconds since epoch
metadata: { ... } // Optional: metadata object
},
// ... more keys
],
list_complete: false, // true if no more keys
cursor: "6Ck1la0VxJ0djhidm1MdX2FyD" // Use for next page
}

IMPORTANT:

  • Keys are always returned in lexicographically sorted order (UTF-8)
  • Always check `list_complete`, not keys.length === 0
  • Empty keys array doesn't mean no more data (expired/deleted keys create "tombstones")
  • When paginating with prefix, you must pass the same prefix with each cursor request

4. Delete Operations
delete() - Delete Key
typescript
// Delete single key
await env.MY_KV.delete('my-key');
// Delete always succeeds, even if key doesn't exist
await env.MY_KV.delete('non-existent-key'); // No error
// Bulk delete pattern (in Worker)
const keysToDelete = ['key1', 'key2', 'key3', ...];
// Delete in parallel (careful of Worker subrequest limits)
await Promise.all(
keysToDelete.map(key => env.MY_KV.delete(key))
);
// For more than 1000 keys, use REST API bulk delete (via wrangler or API)

Bulk Delete via REST API:

The Workers binding doesn't support bulk delete, but you can use the REST API (via wrangler or direct API calls):

bash
# Using wrangler CLI
npx wrangler kv bulk delete --namespace-id=<UUID> keys.json
# keys.json format:
# ["key1", "key2", "key3"]

REST API Limit: Up to 10,000 keys per bulk delete request.


Advanced Patterns & Best Practices

1. Caching Pattern with CacheTtl
typescript
async function getCachedData(
kv: KVNamespace,
cacheKey: string,
fetchFn: () => Promise<any>,
cacheTtl: number = 300
) {
// Try to get from KV cache
const cached = await kv.get(cacheKey, {
type: 'json',
cacheTtl, // Cache at edge for faster subsequent reads
});
if (cached) {
return cached;
}
// Cache miss - fetch fresh data
const data = await fetchFn();
// Store in KV with expiration
await kv.put(cacheKey, JSON.stringify(data), {
expirationTtl: cacheTtl * 2, // Store longer than cache
});
return data;
}
// Usage
app.get('/api/data/:id', async (c) => {
const id = c.req.param('id');
const data = await getCachedData(
c.env.CACHE,
`data:${id}`,
() => fetchFromDatabase(id),
300 // 5 minutes
);
return c.json(data);
});

CacheTtl Guidelines:

  • Minimum: 60 seconds
  • Default: 60 seconds
  • Maximum: Number.MAX_SAFE_INTEGER
  • Use case: Frequently read, infrequently updated data
  • Trade-off: Higher cacheTtl = faster reads but slower update propagation

2. Metadata Optimization Pattern

Store small values in metadata to avoid separate get() calls:

typescript
// ❌ Bad: Two operations
await env.MY_KV.put('user:123', 'active');
const status = await env.MY_KV.get('user:123');
// ✅ Good: Store in metadata with empty value
await env.MY_KV.put('user:123', '', {
metadata: {
status: 'active',
lastSeen: Date.now(),
plan: 'pro'
},
});
// List returns metadata automatically
const users = await env.MY_KV.list({ prefix: 'user:' });
users.keys.forEach(({ name, metadata }) => {
console.log(name, metadata.status, metadata.plan);
// No additional get() calls needed!
});

When to Use:

  • ✅ Values fit in 1024 bytes
  • ✅ You frequently use list() operations
  • ✅ You need to filter/process many keys
  • ❌ Don't use for large values (use regular value storage)

3. Key Coalescing for Performance

Combine related cold keys with hot keys:

typescript
// ❌ Bad: Many individual keys (some hot, some cold)
await kv.put('user:123:name', 'John');
await kv.put('user:123:email', 'john@example.com');
await kv.put('user:123:age', '30');
// ✅ Good: Coalesce into single hot key
await kv.put('user:123', JSON.stringify({
name: 'John',
email: 'john@example.com',
age: 30,
}));

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
14
Repository forks
6
7-day trend score
14

Stars and forks are for the whole GitHub repository, not this skill alone. Refreshed 3 May 2026, 5 months ago.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/jackspace/claudeskillz/cloudflare-kv/badge)](https://verified-skill.com/skills/jackspace/claudeskillz/cloudflare-kv)

Something wrong with this skill or its page? Tell us.