Read, write, and expire values

Use expiration and listing carefully

Set retention at write time and use prefixes and cursors without turning key scans into an application database.

8 minute lesson

~~~

KV can delete keys for you. You declare retention at write time, and no cleanup job ever runs.

There are two forms. expirationTtl counts seconds from the write; expiration sets an absolute Unix timestamp:

await env.SESSIONS.put('session:abc123', payload, { expirationTtl: 3600 })
await env.SESSIONS.put('promo:launch', payload, { expiration: 1767222000 })

The first key vanishes an hour after the write. The second at a fixed moment, useful when the deadline is a date rather than a duration.

Use expiration for caches, one-time state, and data with a clear retention rule — sessions, rate-limit counters, verification codes. One constraint to know: the minimum expiration is 60 seconds from the write. Passing expirationTtl: 30 fails the put, so short windows need a one-minute floor, as in Math.max(60, windowSeconds).

Expiry is also eventually consistent at the edges: a location may serve a cached copy briefly past the deadline. Treat the TTL as retention, and check the timestamp inside the value when lateness matters.

Listing by prefix

Listing is paginated and can filter by prefix. A disciplined key format pays off here:

let cursor
do {
  const page = await env.SESSIONS.list({ prefix: 'session:', cursor })
  for (const key of page.keys) console.log(key.name, key.expiration)
  cursor = page.list_complete ? undefined : page.cursor
} while (cursor)

Each page returns up to 1,000 keys plus a cursor. The loop must check list_complete — code that reads only the first page works in tests and silently drops keys in production.

A list is not an index

A prefix scan is not a relational index, and it is not a consistent snapshot. Keys still propagating may be missing; recently expired keys can linger in results. Avoid building correctness around a complete, globally current list while writes are propagating. “Iterate sessions for cleanup” is fine. “Count keys to enforce a per-tenant quota” is not — store authoritative indexes somewhere with the consistency you need, like D1 or a Durable Object.

Now add a practice key with a short TTL, list its prefix with a cursor-aware loop, then verify the application still behaves after the key expires.

Lesson completed

Take this course offline

Get every free book and course as PDF and EPUB files.

Get the download library →