| 1 | --- |
| 2 | title: KV |
| 3 | description: Cloudflare Workers KV is edge key-value storage — create a namespace, bind it into a Worker with read/write-scoped bindings, and get/put values with metadata. |
| 4 | --- |
| 5 | |
| 6 | Workers KV is Cloudflare's key-value store: values addressed by key, |
| 7 | replicated globally, with eventually-consistent, low-latency reads at |
| 8 | the edge. In alchemy a namespace is a one-line resource, and Workers |
| 9 | reach it through typed, access-scoped bindings. |
| 10 | |
| 11 | Reach for KV when reads dominate and a little staleness is fine — |
| 12 | config, feature flags, sessions, cached API responses, redirects. If |
| 13 | you need relational queries, use [D1](/cloudflare/data/d1); if you need |
| 14 | strongly-consistent state coordinated per object, use |
| 15 | [Durable Object storage](/cloudflare/compute/durable-objects). |
| 16 | |
| 17 | ## Create a namespace |
| 18 | |
| 19 | ```typescript |
| 20 | // src/cache.ts |
| 21 | import * as Cloudflare from "alchemy/Cloudflare"; |
| 22 | |
| 23 | export const Cache = Cloudflare.KV.Namespace("Cache"); |
| 24 | ``` |
| 25 | |
| 26 | Yield it inside your Stack: |
| 27 | |
| 28 | ```typescript |
| 29 | // alchemy.run.ts |
| 30 | import * as Alchemy from "alchemy"; |
| 31 | import * as Cloudflare from "alchemy/Cloudflare"; |
| 32 | import * as Effect from "effect/Effect"; |
| 33 | import { Cache } from "./src/cache.ts"; |
| 34 | |
| 35 | export default Alchemy.Stack( |
| 36 | "MyApp", |
| 37 | { |
| 38 | providers: Cloudflare.providers(), |
| 39 | state: Cloudflare.state(), |
| 40 | }, |
| 41 | Effect.gen(function* () { |
| 42 | const cache = yield* Cache; |
| 43 | |
| 44 | return { namespaceId: cache.namespaceId }; |
| 45 | }), |
| 46 | ); |
| 47 | ``` |
| 48 | |
| 49 | No name required — alchemy generates a unique title from the app, |
| 50 | stage, and logical ID. Pass `title` to control it: |
| 51 | |
| 52 | ```typescript |
| 53 | export const Cache = Cloudflare.KV.Namespace("Cache", { |
| 54 | title: "my-app-cache", |
| 55 | }); |
| 56 | ``` |
| 57 | |
| 58 | ## Bind it to a Worker |
| 59 | |
| 60 | Bind the namespace in the Worker's Construction phase and use the typed |
| 61 | client in the runtime handlers. This Worker stores a value on |
| 62 | `PUT /?key=` and serves it back on `GET /?key=`: |
| 63 | |
| 64 | ```typescript |
| 65 | // src/worker.ts |
| 66 | import * as Cloudflare from "alchemy/Cloudflare"; |
| 67 | import * as Effect from "effect/Effect"; |
| 68 | import { HttpServerRequest } from "effect/http/HttpServerRequest"; |
| 69 | import * as HttpServerResponse from "effect/http/HttpServerResponse"; |
| 70 | import { Cache } from "./cache.ts"; |
| 71 | |
| 72 | export default Cloudflare.Worker( |
| 73 | "Worker", |
| 74 | { main: import.meta.url }, |
| 75 | Effect.gen(function* () { |
| 76 | const kv = yield* Cloudflare.KV.ReadWriteNamespace(Cache); |
| 77 | |
| 78 | return { |
| 79 | fetch: Effect.gen(function* () { |
| 80 | const request = yield* HttpServerRequest; |
| 81 | const url = new URL(request.url, "http://localhost"); |
| 82 | const key = url.searchParams.get("key") ?? ""; |
| 83 | |
| 84 | if (request.method === "PUT") { |
| 85 | const body = yield* request.text; |
| 86 | yield* kv.put(key, body); |
| 87 | return HttpServerResponse.empty({ status: 204 }); |
| 88 | } |
| 89 | |
| 90 | const value = yield* kv.get(key); |
| 91 | if (value === null) { |
| 92 | return HttpServerResponse.text("Not found", { status: 404 }); |
| 93 | } |
| 94 | return HttpServerResponse.text(value); |
| 95 | }).pipe( |
| 96 | Effect.catchTag("NamespaceError", (error) => |
| 97 | Effect.succeed( |
| 98 | HttpServerResponse.text(error.message, { status: 500 }), |
| 99 | ), |
| 100 | ), |
| 101 | ), |
| 102 | }; |
| 103 | }).pipe(Effect.provide(Cloudflare.KV.ReadWriteNamespaceBinding)), |
| 104 | ); |
| 105 | ``` |
| 106 | |
| 107 | KV operations can fail with a typed `NamespaceError` — Effect keeps |
| 108 | it in the type system until you handle it, here with |
| 109 | `Effect.catchTag`. |
| 110 | |
| 111 | ## Typed reads |
| 112 | |
| 113 | `get` defaults to text; pass a type to parse JSON, or an array of |
| 114 | keys for a bulk read: |
| 115 | |
| 116 | ```typescript |
| 117 | const text = yield* kv.get("greeting"); // string | null |
| 118 | const config = yield* kv.get<Config>("config", "json"); // Config | null |
| 119 | const many = yield* kv.get(["a", "b"], "text"); // Map<string, string | null> |
| 120 | ``` |
| 121 | |
| 122 | `arrayBuffer` and `stream` types are also supported for binary |
| 123 | values. |
| 124 | |
| 125 | ## Metadata and expiry |
| 126 | |
| 127 | `put` accepts per-key metadata and a TTL; `getWithMetadata` reads |
| 128 | both back in one call: |
| 129 | |
| 130 | ```typescript |
| 131 | yield* kv.put("session:abc", token, { |
| 132 | metadata: { userId: "u_123" }, |
| 133 | expirationTtl: 3600, // seconds — minimum 60 |
| 134 | }); |
| 135 | |
| 136 | const { value, metadata } = yield* kv.getWithMetadata<{ |
| 137 | userId: string; |
| 138 | }>("session:abc"); |
| 139 | ``` |
| 140 | |
| 141 | ## List keys |
| 142 | |
| 143 | `list` pages through keys, optionally filtered by prefix: |
| 144 | |
| 145 | ```typescript |
| 146 | const result = yield* kv.list({ prefix: "session:" }); |
| 147 | const keys = result.keys.map((k) => k.name); |
| 148 | ``` |
| 149 | |
| 150 | Each entry carries its `name` and any metadata stored alongside the |
| 151 | value. |
| 152 | |
| 153 | ## Delete |
| 154 | |
| 155 | ```typescript |
| 156 | yield* kv.delete("session:abc"); |
| 157 | ``` |
| 158 | |
| 159 | KV is eventually consistent — a fresh write or delete can take a |
| 160 | short while to become visible to reads at other edge locations. |
| 161 | |
| 162 | ## Read, Write, ReadWrite |
| 163 | |
| 164 | The namespace capability is split by access level so each Worker |
| 165 | gets least privilege: |
| 166 | |
| 167 | - `Cloudflare.KV.ReadNamespace` — `get`, `getWithMetadata`, `list`. |
| 168 | - `Cloudflare.KV.WriteNamespace` — `put`, `delete`. |
| 169 | - `Cloudflare.KV.ReadWriteNamespace` — both. |
| 170 | |
| 171 | Each level has two interchangeable implementations you provide as a |
| 172 | layer on the Worker: |
| 173 | |
| 174 | - **`*NamespaceBinding`** (`ReadNamespaceBinding`, |
| 175 | `WriteNamespaceBinding`, `ReadWriteNamespaceBinding`) — a native |
| 176 | KV binding on the Worker, the default choice. |
| 177 | - **`*NamespaceHttp`** (`ReadNamespaceHttp`, `WriteNamespaceHttp`, |
| 178 | `ReadWriteNamespaceHttp`) — the same client over Cloudflare's HTTP |
| 179 | API. Alchemy mints an API token scoped to Workers KV Storage |
| 180 | read/write and binds it into the Worker — for when a native |
| 181 | binding isn't available. |
| 182 | |
| 183 | Your runtime code depends only on the capability |
| 184 | (`ReadNamespace(Cache)` etc.) — swapping the implementation layer |
| 185 | doesn't touch the handlers. |
| 186 | |
| 187 | ## Where next |
| 188 | |
| 189 | Related: |
| 190 | |
| 191 | - [Workers](/cloudflare/compute/workers) — how namespaces get read and |
| 192 | written. |
| 193 | - [D1](/cloudflare/data/d1) — serverless SQLite when you need relational |
| 194 | queries. |
| 195 | - [Durable Objects](/cloudflare/compute/durable-objects) — |
| 196 | strongly-consistent per-object state. |
| 197 | |
| 198 | Reference: |
| 199 | |
| 200 | - [Namespace API reference](/providers/cloudflare/kv/namespace) |