Workers KV -- TTL, Metadata and Bulk Operations
In this tutorial, you'll learn about Workers KV. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
Workers KV supports time-to-live (TTL) expiration for automatic key cleanup, metadata attachment for additional context, and bulk operations for efficiently managing thousands of keys from the edge.
Why TTL, Metadata, and Bulk Operations Matter
Real-world edge storage requires more than simple key-value pairs. TTL automatically evicts stale data like sessions and cache entries without manual cleanup. Metadata lets you attach version numbers, timestamps, and tags to values without Parsing the payload. Bulk operations reduce the number of API calls when handling thousands of keys. These features turn KV from a simple cache into a production-grade storage system for Cloudflare Workers, comparable to traditional REST APIs with database backends.
Real-world use: A feature flag system stores flags in KV with metadata indicating the flag owner and last updated timestamp. TTL is set to 300 seconds for automatic refresh, and bulk list operations aggregate all flags for dashboard display.
KV Expiration and TTL Flow
flowchart LR
P[KV put with TTL] --> T[Timer starts]
T --> A[Key active for TTL duration]
A --> E[TTL expires]
E --> D[Key auto-deleted]
P --> M[Metadata stored separately]
M --> R[getWithMetadata returns metadata]
A --> G[get returns value]
style P fill:#f90,color:#fff
style E fill:#f90,color:#fff
Setting Expiration with TTL
Use expirationTtl to auto-delete keys after a specified number of seconds.
export default {
async fetch(request, env) {
const ttlSeconds = 300;
await env.MY_KV.put('temporary:token', 'eyJhbGciOiJIUzI1NiIs', {
expirationTtl: ttlSeconds
});
const token = await env.MY_KV.get('temporary:token');
return new Response(JSON.stringify({
exists: token !== null,
ttl: ttlSeconds,
message: `Token will expire in ${ttlSeconds} seconds`
}), {
headers: { 'Content-Type': 'application/json' }
});
}
}
Expected output: The token is stored with a 5-minute TTL. Reading immediately returns the token value. After 300 seconds, get returns null as KV automatically removes the expired entry.
Using Absolute Expiration
Use expiration to set an exact Unix timestamp for key expiry.
export default {
async fetch(request, env) {
const tomorrow = Math.floor(Date.now() / 1000) + 86400;
await env.MY_KV.put('daily:report', 'Q4 earnings data', {
expiration: tomorrow
});
const { metadata } = await env.MY_KV.getWithMetadata('daily:report');
return new Response(JSON.stringify({
stored: true,
expiresAt: new Date(tomorrow * 1000).toISOString()
}), {
headers: { 'Content-Type': 'application/json' }
});
}
}
Expected output: The report key is stored with an absolute expiration at midnight of the next day. After the Unix timestamp passes, the key is automatically evicted. Use expiration for fixed-date expiry and expirationTtl for relative durations.
Working with Metadata
Metadata allows you to store contextual information alongside values.
export default {
async fetch(request, env) {
const articleKey = 'articles:edge-computing';
const articleContent = 'Edge computing brings computation closer to data sources...';
await env.MY_KV.put(articleKey, articleContent, {
metadata: {
author: 'DodaTech',
wordCount: 1240,
tags: ['edge', 'cloudflare', 'serverless'],
publishedAt: new Date().toISOString()
}
});
const { value, metadata } = await env.MY_KV.getWithMetadata(articleKey);
return new Response(JSON.stringify({
metadata,
contentLength: value.length
}), {
headers: { 'Content-Type': 'application/json' }
});
}
}
Expected output: Returns the metadata object with author, word count, tags, and publish date. The content is retrieved separately. Metadata is limited to 1024 bytes and must be valid JSON. This pattern is useful for content management systems where you need to query article metadata without loading full content.
Bulk Deletion with List and Delete
Combine list with prefix filtering and delete to remove groups of keys.
export default {
async fetch(request, env) {
const oldPrefix = 'temp:';
const { keys } = await env.MY_KV.list({ prefix: oldPrefix });
let deleted = 0;
for (const key of keys) {
await env.MY_KV.delete(key.name);
deleted++;
}
return new Response(JSON.stringify({
deletedCount: deleted,
prefix: oldPrefix
}), {
headers: { 'Content-Type': 'application/json' }
});
}
}
Expected output: Lists all keys with the temp: prefix and deletes each one. Returns the count of deleted keys. For large numbers of keys, paginate through results using the cursor parameter from the list response, as each list call returns a maximum of 1000 keys.
Common Errors
| Error | Cause | Fix |
|---|---|---|
expirationTtl must be at least 60 seconds |
TTL set to less than 60 seconds | Use a minimum TTL of 60 or use expiration with an absolute timestamp |
Metadata exceeds 1024 bytes |
Metadata JSON too large | Reduce metadata to essential fields under 1024 bytes total |
List cursor expired |
Pagination cursor is stale | Request a fresh list with the same prefix; cursors expire after a few minutes |
Invalid expiration timestamp |
expiration is in the past |
Ensure the Unix timestamp is in the future |
Cannot set both expiration and expirationTtl |
Both parameters provided | Choose one: expiration for absolute time or expirationTtl for relative duration |
Practice Questions
- What is the minimum TTL value you can set with
expirationTtl? - How does
getWithMetadatadiffer fromgetwhen metadata is attached to a key? - Why would you use
expiration(absolute timestamp) instead ofexpirationTtl(relative seconds)?
FAQ
Summary
TTL-based expiration automatically cleans up stale data, metadata provides context without Parsing values, and bulk operations handle large-scale key management. These features transform Workers KV from a simple cache into a robust edge storage system suitable for sessions, feature flags, content management, and configuration distribution. Doda Browser uses these patterns to cache user preferences with per-key metadata tracking across its global edge deployment. Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro -- security-first tools for the modern web.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro