null-shot/cloudflare-skills

kv

Eventually-consistent key-value storage distributed globally. Load when implementing session storage, authentication tokens, caching API responses, feature flags, A/B testing, rate limiting, or storing user preferences at the edge.

First seen Feb 16, 2026

Installation

$ npx skills add null-shot/cloudflare-skills --skill kv

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from null-shot/cloudflare-skills · top by installs.

npx skills add null-shot/cloudflare-skills

Browse all from null-shot/cloudflare-skills

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

License LICENSE
Default branch main
Status Active

Skill metadata

Parsed from SKILL.md frontmatter.

Declared agents cursor

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 8,392 B
  • docs SUMMARY.md 241 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 5 installs

SKILL.md

Workers KV

Fast, eventually-consistent key-value storage distributed globally across Cloudflare's edge network.

FIRST: Create KV Namespace

# Create namespace
wrangler kv namespace create MY_KV

# Create preview namespace for dev
wrangler kv namespace create MY_KV --preview

# Add to wrangler.jsonc (use IDs from commands above)

When to Use

Use Case Why KV
Session storage Fast token/session lookups at the edge
Caching Cache API responses, rendered HTML, or computed results
Configuration data Feature flags, app settings, rate limit counters
User profiles Store user preferences, settings, metadata
A/B testing Store experiment assignments and feature toggles
Rate limiting Track request counts per user/IP with TTL

Don't use KV for:

  • Strongly consistent data (use Durable Objects)
  • Relational queries (use D1)
  • Large files >25MB (use R2)
  • Transactional operations

Quick Reference

Operation API Example
Read value get(key) await env.KV.get("user:123")
Read with metadata getWithMetadata(key) await env.KV.getWithMetadata("key", "json")
Write value put(key, value, options) await env.KV.put("key", "value", { expirationTtl: 3600 })
Delete value delete(key) await env.KV.delete("key")
List keys list(options) await env.KV.list({ prefix: "user:" })

Value Types

// String (default)
await env.KV.get("key")

// JSON (auto-parse)
await env.KV.get("user:123", "json")

// Binary
await env.KV.get("image", "arrayBuffer")

// Stream
await env.KV.get("large-file", "stream")

wrangler.jsonc Configuration

{
  "name": "my-worker",
  "main": "src/index.ts",
  "compatibility_date": "2026-01-01",
  
  "kv_namespaces": [
    {
      "binding": "AUTH_TOKENS",
      "id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
      "preview_id": "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"
    },
    {
      "binding": "CACHE",
      "id": "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"
    }
  ]
}

After adding bindings, generate types:

wrangler types

Session Authentication Pattern

Complete session management using KV for token storage with Hono router:

// src/index.ts
import { Hono } from 'hono'
import { cors } from 'hono/cors'

interface Env {
  AUTH_TOKENS: KVNamespace;
}

const app = new Hono<{ Bindings: Env }>()

// Add CORS middleware
app.use('*', cors())

app.get('/', async (c) => {
  try {
    // Get token from header or cookie
    const token = c.req.header('Authorization')?.slice(7) ||
      c.req.header('Cookie')?.match(/auth_token=([^;]+)/)?.[1];
    
    if (!token) {
      return c.json({
        authenticated: false,
        message: 'No authentication token provided'
      }, 403)
    }

    // Check token in KV
    const userData = await c.env.AUTH_TOKENS.get(token)

    if (!userData) {
      return c.json({
        authenticated: false,
        message: 'Invalid or expired token'
      }, 403)
    }

    return c.json({
      authenticated: true,
      message: 'Authentication successful',
      data: JSON.parse(userData)
    })

  } catch (error) {
    console.error('Authentication error:', error)
    return c.json({
      authenticated: false,
      message: 'Internal server error'
    }, 500)
  }
})

export default app

Store a session token:

// Create token (e.g., on login)
const token = crypto.randomUUID()
const userData = { userId: 123, email: "[email protected]" }

// Store with 24-hour expiration
await env.AUTH_TOKENS.put(
  token, 
  JSON.stringify(userData),
  { expirationTtl: 86400 }
)

Key Design Patterns

TTL (Time-to-Live)

// Expire after 1 hour
await env.KV.put("cache:api-response", data, {
  expirationTtl: 3600
})

// Expire at specific time
await env.KV.put("promo:summer", data, {
  expiration: Math.floor(Date.now() / 1000) + 86400
})

Key Naming Conventions

// Use prefixes for organization and listing
"user:123"           // User data
"session:abc123"     // Session tokens
"cache:api:/users"   // API cache
"config:feature:new" // Feature flags
"rate:ip:1.2.3.4"    // Rate limiting

// List all user keys
const { keys } = await env.KV.list({ prefix: "user:" })

Metadata

// Store metadata with value
await env.KV.put("user:123", userData, {
  metadata: { 
    created: Date.now(),
    version: "v1",
    tags: ["premium", "verified"]
  }
})

// Read with metadata
const { value, metadata } = await env.KV.getWithMetadata("user:123", "json")
console.log(metadata.tags) // ["premium", "verified"]

Caching Pattern

async function getCachedData(env: Env, key: string) {
  // Try cache first
  const cached = await env.CACHE.get(key, "json")
  if (cached) return cached

  // Fetch from origin
  const data = await fetchFromAPI()

  // Cache for 5 minutes
  await env.CACHE.put(key, JSON.stringify(data), {
    expirationTtl: 300
  })

  return data
}

Rate Limiting

async function checkRateLimit(env: Env, ip: string): Promise<boolean> {
  const key = `rate:${ip}`
  const count = await env.KV.get(key)

  if (!count) {
    // First request
    await env.KV.put(key, "1", { expirationTtl: 60 })
    return true
  }

  const current = parseInt(count)
  if (current >= 100) {
    return false // Rate limit exceeded
  }

  // Increment counter
  await env.KV.put(key, String(current + 1), { expirationTtl: 60 })
  return true
}

Edge Cases

Handling 404s (Key Not Found)

const value = await env.KV.get("key")

if (value === null) {
  // Key doesn't exist or has expired
  console.log("Key not found")
}

// For JSON, check before parsing
const userData = await env.KV.get("user:123")
if (userData) {
  const user = JSON.parse(userData)
}

// Or use "json" type (returns null if not found)
const user = await env.KV.get("user:123", "json")

Eventual Consistency

// Write may take seconds to propagate globally
await env.KV.put("counter", "1")

// Immediate read might return old value or null
const value = await env.KV.get("counter") // Could be null or "1"

// For strong consistency, use Durable Objects instead

Size Limits

// Keys: max 512 bytes
// Values: max 25 MB
// Metadata: max 1024 bytes

// For larger data, use R2:
if (data.length > 25 * 1024 * 1024) {
  await env.BUCKET.put("large-file", data)
} else {
  await env.KV.put("small-file", data)
}

List Pagination

// List returns max 1000 keys per call
let cursor: string | undefined
const allKeys: string[] = []

do {
  const result = await env.KV.list({ 
    prefix: "user:",
    cursor 
  })
  
  allKeys.push(...result.keys.map(k => k.name))
  cursor = result.list_complete ? undefined : result.cursor
} while (cursor)

Detailed References

  • [references/patterns.md](references/patterns.md) - Advanced patterns: cache invalidation, distributed locks, A/B testing
  • [references/limits.md](references/limits.md) - Size limits, rate limits, consistency model
  • [references/testing.md](references/testing.md) - Vitest integration, mocking KV, test isolation

Best Practices

  1. Use TTL for temporary data: Always set expiration for sessions, caches, rate limits
  2. Prefix your keys: Makes listing and management easier (user:, session:, cache:)
  3. Don't rely on immediate consistency: KV is eventually consistent (usually <60s)
  4. Handle null values: Keys not found or expired return null
  5. Use metadata for filtering: Store timestamps, versions, tags without reading values
  6. Batch operations: Use list() with prefix instead of individual gets
  7. Cache at the edge: KV is fastest when accessed from same region as user
  8. Monitor costs: Writes are more expensive than reads, optimize write patterns
  9. Use Durable Objects for coordination: Don't use KV for locks or transactions
  10. Generate types: Run wrangler types after config changes for type safety