KV
Workers KV is a key-value store copied to every city in Cloudflare’s network. A Worker reads a key from the copy nearest the request, so reads are fast everywhere. Writes go to a central store and spread out from there.
Fast reads, eventual writes
Section titled “Fast reads, eventual writes”A value written in one city reaches the others within about a minute. Until it does, a read elsewhere can return the old value, or nothing.
So KV fits data that’s read far more often than it’s written, where a moment of staleness is fine: configuration, feature flags, sessions, cached API responses, redirects. For relational data, use D1. For state that must be consistent the instant it’s written, use a Durable Object.
Declare a namespace
Section titled “Declare a namespace”Keys live in a namespace:
import * as Cloudflare from "@alchemy.run/cloudflare";
export const Cache = Cloudflare.KV.Namespace("Cache");Alchemy names it from your app, stage and id, so each stage gets its own.
Read and write values
Section titled “Read and write values”Bind the namespace in a Worker and get a typed client. ReadWriteNamespace gives it both reads and writes:
export default Cloudflare.Worker( "Worker", { main: import.meta.url }, Effect.gen(function* () { const kv = yield* Cloudflare.KV.ReadWriteNamespace(Cache); return { fetch: Effect.gen(function* () { yield* kv.put("greeting", "hello"); const value = yield* kv.get("greeting"); return HttpServerResponse.text(value ?? "not found"); }).pipe(Effect.orDie), }; }).pipe(Effect.provide(Cloudflare.KV.ReadWriteNamespaceBinding)),);A missing key reads as null. Every operation can fail with a NamespaceError, which stays in the type until you handle it.
Read typed values
Section titled “Read typed values”get returns text by default. Ask for JSON and give its type, or read several keys at once:
const config = yield* kv.get<Config>("config", "json");const many = yield* kv.get(["a", "b"], "text");"arrayBuffer" and "stream" read binary values.
Expire keys and attach metadata
Section titled “Expire keys and attach metadata”A key can expire on its own, after at least 60 seconds. It can also carry metadata, read back with the value in one call:
yield* kv.put(`session:${id}`, token, { expirationTtl: 3600, metadata: { userId },});
const { value, metadata } = yield* kv.getWithMetadata<{ userId: string }>(`session:${id}`);List keys
Section titled “List keys”list returns keys a page at a time, optionally those starting with a prefix:
const page = yield* kv.list({ prefix: "session:" });const names = page.keys.map((key) => key.name);Each key comes with its metadata. Put what you need to show in a list into metadata, and you can render the list without reading every value.
Ask for only the access you need
Section titled “Ask for only the access you need”The client comes in three access levels. ReadNamespace can get, getWithMetadata and list. WriteNamespace can put and delete. ReadWriteNamespace can do both. A Worker that only serves configuration should bind ReadNamespace, so its code can’t change it.
Each level has two implementations, chosen by the Layer the Worker provides. The *NamespaceBinding Layers use a native KV binding on the Worker. The *NamespaceHttp Layers use Cloudflare’s API instead, with an API token Alchemy creates for just that namespace’s access, for code running outside a Worker. The handlers don’t change between them.