Skip to main content
Workers KV provides low-latency, high-throughput global key-value storage. It’s optimized for high read volumes and infrequent writes, making it ideal for storing configuration data, user preferences, and cached content.

Overview

KV namespaces provide:
  • Global distribution with edge caching
  • String and binary value storage
  • Metadata support for each key
  • Bulk read operations
  • Time-to-live (TTL) for automatic expiration
Implementation: src/workerd/api/kv.h and kv.c++

Accessing KV namespaces

KV namespaces are bound to your worker in the environment:
export default {
  async fetch(request, env) {
    // Access via env binding
    const value = await env.MY_KV.get('key');
    return new Response(value);
  }
};

Reading values

Get as text

Retrieve a value as a string:
const value = await env.MY_KV.get('key');
if (value === null) {
  return new Response('Not found', { status: 404 });
}
return new Response(value);

Get as JSON

Parse JSON automatically:
const data = await env.MY_KV.get('user:123', 'json');
if (data === null) {
  return new Response('Not found', { status: 404 });
}
console.log(data.name, data.email);
Source: src/workerd/api/tests/kv-test.js:20

Get as ArrayBuffer

Retrieve binary data:
const buffer = await env.MY_KV.get('image', 'arrayBuffer');
if (buffer === null) {
  return new Response('Not found', { status: 404 });
}
return new Response(buffer, {
  headers: { 'Content-Type': 'image/png' }
});

Get as stream

Stream large values efficiently:
const stream = await env.MY_KV.get('large-file', 'stream');
if (stream === null) {
  return new Response('Not found', { status: 404 });
}
return new Response(stream);

Get options

Customize retrieval behavior:
const value = await env.MY_KV.get('key', {
  type: 'text',
  cacheTtl: 3600  // Cache at the edge for 1 hour
});
Source: src/workerd/api/kv.h:43

Writing values

Put text

Store a string value:
await env.MY_KV.put('key', 'value');

Put JSON

Store an object as JSON:
const data = { name: 'Alice', email: '[email protected]' };
await env.MY_KV.put('user:123', JSON.stringify(data));

Put binary data

Store binary data:
const buffer = new Uint8Array([1, 2, 3, 4]);
await env.MY_KV.put('binary', buffer);

Put with options

Set expiration and metadata:
await env.MY_KV.put('session:abc', sessionData, {
  expirationTtl: 3600,  // Expire in 1 hour
  metadata: { userId: '123', created: Date.now() }
});

// Or use absolute expiration time
await env.MY_KV.put('key', 'value', {
  expiration: Math.floor(Date.now() / 1000) + 3600  // Unix timestamp
});
Source: src/workerd/api/kv.h:122

Metadata

Store metadata alongside values:
// Write with metadata
await env.MY_KV.put('user:123', userData, {
  metadata: {
    lastModified: Date.now(),
    version: 2
  }
});

// Read with metadata
const result = await env.MY_KV.getWithMetadata('user:123');
if (result.value !== null) {
  console.log('Value:', result.value);
  console.log('Metadata:', result.metadata);
  console.log('Cache status:', result.cacheStatus);
}
Source: src/workerd/api/kv.h:79

Listing keys

List keys in a namespace:
// List all keys
const result = await env.MY_KV.list();
console.log('Keys:', result.keys);
console.log('Complete:', result.list_complete);

// List with prefix
const users = await env.MY_KV.list({
  prefix: 'user:'
});

// List with cursor for pagination
let cursor;
do {
  const result = await env.MY_KV.list({
    prefix: 'user:',
    limit: 10,
    cursor
  });
  
  for (const key of result.keys) {
    console.log(key.name, key.metadata);
  }
  
  cursor = result.cursor;
} while (!result.list_complete);
Source: src/workerd/api/tests/kv-test.js:72

List options

Customize listing behavior:
const result = await env.MY_KV.list({
  prefix: 'user:',     // Filter by prefix
  limit: 100,          // Max keys to return (default 1000)
  cursor: 'abc123'     // Pagination cursor
});
Source: src/workerd/api/kv.h:109

Deleting values

Remove a key:
await env.MY_KV.delete('key');

Bulk operations

Bulk get

Retrieve multiple keys in a single operation (experimental):
const keys = ['key1', 'key2', 'key3'];
const results = await env.MY_KV.get(keys, 'json');

// Results is a Map
for (const [key, value] of results) {
  console.log(`${key}:`, value);
}
Source: src/workerd/api/tests/kv-test.js:23

Bulk get with metadata

const keys = ['key1', 'key2', 'key3'];
const results = await env.MY_KV.getWithMetadata(keys, 'json');

// Results is a Map of objects with value and metadata
for (const [key, result] of results) {
  console.log(`${key}:`, result.value, result.metadata);
}
Bulk operations are experimental and require the workerdExperimental compatibility flag.

Patterns

Configuration storage

Store application configuration:
// Write config
const config = {
  apiKey: 'secret',
  endpoint: 'https://api.example.com',
  timeout: 5000
};
await env.CONFIG.put('app-config', JSON.stringify(config));

// Read config
const config = await env.CONFIG.get('app-config', 'json');

User preferences

Store per-user settings:
// Save preferences
const prefs = {
  theme: 'dark',
  language: 'en',
  notifications: true
};
await env.PREFS.put(
  `user:${userId}:prefs`,
  JSON.stringify(prefs),
  { metadata: { updated: Date.now() } }
);

// Load preferences
const prefs = await env.PREFS.get(
  `user:${userId}:prefs`,
  'json'
) || getDefaultPrefs();

Cache with TTL

Cache API responses:
export default {
  async fetch(request, env) {
    const cacheKey = `cache:${request.url}`;
    
    // Try cache first
    const cached = await env.CACHE.get(cacheKey);
    if (cached !== null) {
      return new Response(cached);
    }
    
    // Fetch from origin
    const response = await fetch(request);
    const data = await response.text();
    
    // Cache for 1 hour
    await env.CACHE.put(cacheKey, data, {
      expirationTtl: 3600
    });
    
    return new Response(data);
  }
};

Feature flags

Implement feature toggles:
class FeatureFlags {
  constructor(kv) {
    this.kv = kv;
  }
  
  async isEnabled(feature) {
    const value = await this.kv.get(`feature:${feature}`);
    return value === 'true';
  }
  
  async enable(feature) {
    await this.kv.put(`feature:${feature}`, 'true');
  }
  
  async disable(feature) {
    await this.kv.put(`feature:${feature}`, 'false');
  }
}

const flags = new FeatureFlags(env.FLAGS);
if (await flags.isEnabled('new-ui')) {
  // Serve new UI
}

Best practices

Organize keys with prefixes for easier management:
// Good: organized with prefixes
await env.KV.put('user:123:profile', data);
await env.KV.put('user:123:prefs', prefs);
await env.KV.put('config:app', config);

// Bad: flat namespace
await env.KV.put('user123profile', data);
Use TTL to automatically clean up temporary data:
// Session data expires in 1 hour
await env.KV.put('session:' + id, data, {
  expirationTtl: 3600
});
Always check if a value exists:
const value = await env.KV.get('key');
if (value === null) {
  // Key doesn't exist or has expired
  return getDefaultValue();
}
Store small amounts of metadata with values:
await env.KV.put('data', value, {
  metadata: {
    version: 1,
    created: Date.now(),
    author: 'system'
  }
});

Limitations

  • Maximum key size: 512 bytes (UTF-8)
  • Maximum value size: 25 MB
  • Maximum metadata size: 1 KB
  • List operations return maximum 1000 keys per request
  • Write operations are eventually consistent
  • Read operations can be cached at the edge

Implementation details

The KV API is implemented in:
  • src/workerd/api/kv.h - Interface definition (283 lines)
  • src/workerd/api/kv.c++ - Implementation
KV operations use HTTP client connections through the subrequest channel:
class KvNamespace: public jsg::Object {
  struct GetOptions {
    jsg::Optional<kj::String> type;
    jsg::Optional<int> cacheTtl;
  };

  struct PutOptions {
    jsg::Optional<int> expiration;
    jsg::Optional<int> expirationTtl;
    jsg::Optional<kj::Maybe<jsg::JsRef<jsg::JsValue>>> metadata;
  };

  jsg::Promise<GetResult> get(
    jsg::Lock& js,
    kj::OneOf<kj::String, kj::Array<kj::String>> name,
    jsg::Optional<kj::OneOf<kj::String, GetOptions>> options);

  jsg::Promise<void> put(
    jsg::Lock& js,
    kj::String name,
    PutBody body,
    jsg::Optional<PutOptions> options);
};
Source: src/workerd/api/kv.h:22

Build docs developers (and LLMs) love