TinyStore

KV / Expiry

Expiry

A key in a KV bucket can expire: a fixed time after it was written, at a set time, or after a span without reads or writes. An expired key is gone for every call at once, and nothing has to clean it up.

const codes = store.bucket<number>('sign-in-codes', { ttl: '15m' })
const sessions = store.bucket<Session>('sessions', { idle: '30d' })

await codes.set(digest(code), userId) // expires 15 minutes from now
await codes.set(digest(code), userId, { ttl: '1h' }) // this key's own term
await codes.set(digest(code), userId, { expiresAt: endOfDay }) // at a time
await codes.expire(digest(code), '5m') // a new term for a key that exists
codes = store.bucket("sign-in-codes", int, ttl="15m")
sessions = store.bucket("sessions", Session, idle="30d")

await codes.set(digest(code), user_id)  # expires 15 minutes from now
await codes.set(digest(code), user_id, ttl="1h")  # this key's own term
await codes.set(digest(code), user_id, expires_at=end_of_day)  # at a time
await codes.expire(digest(code), "5m")  # a new term for a key that exists
codes, err := kv.OpenBucket[int64](store, "sign-in-codes", kv.TTL(15*time.Minute))
sessions, err := kv.OpenBucket[Session](store, "sessions", kv.Idle(30*24*time.Hour))

err = codes.Set(ctx, digest(code), userID)                   // expires 15 minutes from now
err = codes.Set(ctx, digest(code), userID, kv.TTL(time.Hour)) // this key's own term
err = codes.Expire(ctx, digest(code), 5*time.Minute)          // a new term for a key that exists
let codes = store.bucket::<i64>("sign-in-codes").ttl(Duration::from_mins(15)).open()?;
let sessions = store.bucket::<Session>("sessions").idle(Duration::from_hours(24 * 30)).open()?;

codes.set(digest(&code), &user_id)?; // expires 15 minutes from now
codes.key(digest(&code)).ttl(Duration::from_hours(1)).set(&user_id)?; // this key's own term
codes.expire(digest(&code), Duration::from_mins(5))?; // a new term for a key that exists

The three kinds of term

OptionThe key expiresFor
ttl: '15m'15 minutes after it was writtencodes, caches, links that expire
expiresAt: dateat that timea trial that ends on a date
idle: '30d'30 days after it was last read or writtensessions of active users
nonenever, unless the bucket's default or the call sets one

The bucket's ttl or idle is the default for every key written without its own. A bucket has one or the other, never both. expiresAt is expires_at in Python and kv.ExpiresAt in Go, and expire takes a time as ExpireAt in Go.

What "expired" means

An expired key is absent to every call at once (contract KV-03): get returns nothing, create and add can write it again, and a write with ifVersion fails conflict. The store deletes the row later, in the background, so expiry costs no write at the moment it happens.

Idle keys

A get, entry or has of an idle key renews its span. To keep reads cheap, the renewal is written only once a thirtieth of the span has passed, about once a day for 30 days, together with other writes a second later. list and all renew nothing, so a page that lists every session does not keep them alive.

Limits and defaults

WhatDefault
a bucket's termnone: keys never expire
an idle key's renewalonce a thirtieth of its span has passed
when a row is deletedin the background, after it expired

See also

Edit this page on GitHub Updated 19 October 2026

esc
↑↓ navigate ↵ open ⇥ filter 0 results