1---
2title: KV
3description: 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
6Workers KV is Cloudflare's key-value store: values addressed by key,
7replicated globally, with eventually-consistent, low-latency reads at
8the edge. In alchemy a namespace is a one-line resource, and Workers
9reach it through typed, access-scoped bindings.
10
11Reach for KV when reads dominate and a little staleness is fine —
12config, feature flags, sessions, cached API responses, redirects. If
13you need relational queries, use [D1](/cloudflare/data/d1); if you need
14strongly-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
21import * as Cloudflare from "alchemy/Cloudflare";
22
23export const Cache = Cloudflare.KV.Namespace("Cache");
24```
25
26Yield it inside your Stack:
27
28```typescript
29// alchemy.run.ts
30import * as Alchemy from "alchemy";
31import * as Cloudflare from "alchemy/Cloudflare";
32import * as Effect from "effect/Effect";
33import { Cache } from "./src/cache.ts";
34
35export 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
49No name required — alchemy generates a unique title from the app,
50stage, and logical ID. Pass `title` to control it:
51
52```typescript
53export const Cache = Cloudflare.KV.Namespace("Cache", {
54 title: "my-app-cache",
55});
56```
57
58## Bind it to a Worker
59
60Bind the namespace in the Worker's Construction phase and use the typed
61client 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
66import * as Cloudflare from "alchemy/Cloudflare";
67import * as Effect from "effect/Effect";
68import { HttpServerRequest } from "effect/http/HttpServerRequest";
69import * as HttpServerResponse from "effect/http/HttpServerResponse";
70import { Cache } from "./cache.ts";
71
72export 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
107KV operations can fail with a typed `NamespaceError` — Effect keeps
108it 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
114keys for a bulk read:
115
116```typescript
117const text = yield* kv.get("greeting"); // string | null
118const config = yield* kv.get<Config>("config", "json"); // Config | null
119const many = yield* kv.get(["a", "b"], "text"); // Map<string, string | null>
120```
121
122`arrayBuffer` and `stream` types are also supported for binary
123values.
124
125## Metadata and expiry
126
127`put` accepts per-key metadata and a TTL; `getWithMetadata` reads
128both back in one call:
129
130```typescript
131yield* kv.put("session:abc", token, {
132 metadata: { userId: "u_123" },
133 expirationTtl: 3600, // seconds — minimum 60
134});
135
136const { 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
146const result = yield* kv.list({ prefix: "session:" });
147const keys = result.keys.map((k) => k.name);
148```
149
150Each entry carries its `name` and any metadata stored alongside the
151value.
152
153## Delete
154
155```typescript
156yield* kv.delete("session:abc");
157```
158
159KV is eventually consistent — a fresh write or delete can take a
160short while to become visible to reads at other edge locations.
161
162## Read, Write, ReadWrite
163
164The namespace capability is split by access level so each Worker
165gets least privilege:
166
167- `Cloudflare.KV.ReadNamespace` — `get`, `getWithMetadata`, `list`.
168- `Cloudflare.KV.WriteNamespace` — `put`, `delete`.
169- `Cloudflare.KV.ReadWriteNamespace` — both.
170
171Each level has two interchangeable implementations you provide as a
172layer 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
183Your runtime code depends only on the capability
184(`ReadNamespace(Cache)` etc.) — swapping the implementation layer
185doesn't touch the handlers.
186
187## Where next
188
189Related:
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
198Reference:
199
200- [Namespace API reference](/providers/cloudflare/kv/namespace)