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.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "kv" with this command: npx skills add null-shot/cloudflare-skills/null-shot-cloudflare-skills-kv

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 CaseWhy KV
Session storageFast token/session lookups at the edge
CachingCache API responses, rendered HTML, or computed results
Configuration dataFeature flags, app settings, rate limit counters
User profilesStore user preferences, settings, metadata
A/B testingStore experiment assignments and feature toggles
Rate limitingTrack 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

OperationAPIExample
Read valueget(key)await env.KV.get("user:123")
Read with metadatagetWithMetadata(key)await env.KV.getWithMetadata("key", "json")
Write valueput(key, value, options)await env.KV.put("key", "value", { expirationTtl: 3600 })
Delete valuedelete(key)await env.KV.delete("key")
List keyslist(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: "user@example.com" }

// 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

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

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

cloudflare-opennext

No summary provided by upstream source.

Repository SourceNeeds Review
General

r2-storage

No summary provided by upstream source.

Repository SourceNeeds Review
General

queues

No summary provided by upstream source.

Repository SourceNeeds Review
General

workers

No summary provided by upstream source.

Repository SourceNeeds Review