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
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
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
Use appropriate key naming
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 );
Set expiration on temporary 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 ();
}
Use metadata for auxiliary data
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