Claude
Skills
Sign in
Back

cloudflare-kv

Included with Lifetime
$97 forever

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

Backend & APIs

What this skill does


# Cloudflare Workers KV

**Status**: Production Ready ✅
**Last Updated**: 2025-10-21
**Dependencies**: cloudflare-worker-base (for Worker setup)
**Latest Versions**: [email protected], @cloudflare/[email protected]

---

## 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: 1234

Related in Backend & APIs