State & Cache
Key-value storage, durable or fast
Two key-value stores for two jobs. State is durable, backed by SQLite, and exposes a KV-compatible API plus atomic JSON updates, so counters and documents never need a read-modify-write cycle. Cache is a fast, best-effort store with optional TTLs for rate limits, sessions, and computed results.
State & Cache in Action
Keys
Durable state has no expiry; cache entries carry a TTL
| Key | Value | Store | TTL |
|---|---|---|---|
| stats | { "visits": 10482, "signups": 231 } | STATE | — |
| user:123 | { "role": "admin", "visits": 42 } | STATE | — |
| feature:dark-mode | "true" | STATE | — |
| report:q3 | <cached 14 KB> | CACHE | 9m 12s |
| ratelimit:203.0.113.4 | "1" | CACHE | 0.4s |
Atomic Update
Increment a counter without reading it first
await env.STATE.update("user:123", {
$inc: { visits: 1 },
$set: { lastSeen: Date.now() },
});Key Features
KV-Compatible State
get, put, delete, getWithMetadata, and list with the same shapes as the KV API you already know.
Atomic JSON Updates
$set, $inc, $push, $pop, and $unset apply directly in storage, so concurrent writers never clobber each other.
Cache With TTL
Set a value for sixty seconds or an hour and let it expire. Treat it as best-effort and fall back when it is gone.
Multiple Bindings
Attach several state or cache stores to one worker and keep user data separate from config.
State and Cache Bindings
Durable state and an ephemeral cache, side by side in the same worker.
export default {
async fetch(request, env) {
await env.STATE.update("stats", { $inc: { visits: 1 } });
const cached = await env.CACHE.get("report");
if (cached) {
return new Response(cached);
}
const report = await buildReport(env);
await env.CACHE.set("report", report, { ttl: 600 });
return new Response(report);
},
} satisfies Ploy;