1---
2title: Rate limiting
3description: Throttle requests inside a Worker with Cloudflare's Rate Limiting binding — declare the limit on the binding, count arbitrary keys at runtime, handle failures as typed errors.
4sidebar:
5 order: 12
6---
7
8The Rate Limiting binding gives a Worker a counter it can consult on
9every request. Pass a key, such as an IP, a user id, or an API token,
10and get back whether that key is within its budget. There is no backing cloud
11resource to deploy; the limit configuration lives on the binding
12itself and ships with the Worker.
13
14## Bind a rate limit to a Worker
15
16`yield*` `Cloudflare.RateLimit(name, props)` in the Worker's Construction
17phase and provide `RateLimitBinding` as a layer:
18
19```typescript
20// src/worker.ts
21import * as Cloudflare from "alchemy/Cloudflare";
22import * as Effect from "effect/Effect";
23import { HttpServerRequest } from "effect/http/HttpServerRequest";
24import * as HttpServerResponse from "effect/http/HttpServerResponse";
25
26export default Cloudflare.Worker(
27 "ThrottledWorker",
28 { main: import.meta.url },
29 Effect.gen(function* () {
30 const throttle = yield* Cloudflare.RateLimit("THROTTLE", {
31 namespaceId: 1001,
32 simple: { limit: 10, period: 60 },
33 });
34
35 return {
36 fetch: Effect.gen(function* () {
37 const request = yield* HttpServerRequest;
38 const ip = request.headers["cf-connecting-ip"] ?? "unknown";
39
40 const { success } = yield* throttle
41 .limit({ key: ip })
42 .pipe(Effect.orDie);
43
44 return success
45 ? HttpServerResponse.text("ok")
46 : HttpServerResponse.text("rate limited", { status: 429 });
47 }),
48 };
49 }).pipe(Effect.provide(Cloudflare.Workers.RateLimitBinding)),
50);
51```
52
53`simple.limit` is the number of requests allowed per `simple.period`
54seconds. The period must be `10` or `60`. `namespaceId` is a number
55or string that uniquely identifies this limit configuration within
56the account; reuse the same id to share one budget across Workers,
57use distinct ids for independent limits.
58
59## Count independent keys
60
61Every key passed to `limit` gets its own budget. With
62`limit: 2, period: 10`, two calls for `"alice"` succeed and the third
63is denied, while `"bob"` still has a full budget:
64
65```typescript
66const a1 = yield* throttle.limit({ key: "alice" }); // { success: true }
67const a2 = yield* throttle.limit({ key: "alice" }); // { success: true }
68const a3 = yield* throttle.limit({ key: "alice" }); // { success: false }
69const b1 = yield* throttle.limit({ key: "bob" }); // { success: true }
70```
71
72Counting is approximate and local to the Cloudflare location serving
73the request. The binding is built for cheap, fast abuse protection,
74not globally consistent quotas. For exact global counting, put the
75counter in a [Durable Object](/cloudflare/compute/durable-objects).
76
77## Handle failures as typed errors
78
79`limit` fails with a typed `RateLimitError` if the runtime call
80itself blows up (as opposed to the key being over budget, which is
81the `success: false` case). It lives in the Effect error channel like
82any other tagged error:
83
84```typescript
85const { success } = yield* throttle.limit({ key }).pipe(
86 // fail open: allow the request if the limiter itself errors
87 Effect.catchTag("RateLimitError", () =>
88 Effect.succeed({ success: true }),
89 ),
90);
91```
92
93## Declare the binding as env metadata
94
95Workers whose `main` module exports a plain async `fetch` handler
96declare the limit on `env` instead. `InferEnv` types it as
97Cloudflare's native `RateLimit` binding:
98
99```typescript
100// alchemy.run.ts
101export const Worker = Cloudflare.Worker("Worker", {
102 main: "./src/worker.ts",
103 env: {
104 THROTTLE: Cloudflare.RateLimit("THROTTLE", {
105 namespaceId: 1001,
106 simple: { limit: 10, period: 60 },
107 }),
108 },
109});
110
111export type WorkerEnv = Cloudflare.InferEnv<typeof Worker>;
112```
113
114```typescript
115// src/worker.ts
116import type { WorkerEnv } from "../alchemy.run.ts";
117
118export default {
119 async fetch(req: Request, env: WorkerEnv) {
120 const { success } = await env.THROTTLE.limit({
121 key: req.headers.get("cf-connecting-ip") ?? "unknown",
122 });
123 return new Response(success ? "ok" : "rate limited", {
124 status: success ? 200 : 429,
125 });
126 },
127};
128```
129
130## Where next
131
132- [Workers](/cloudflare/compute/workers) — the two-phase Worker model
133 the binding lives inside.
134- [Durable Objects](/cloudflare/compute/durable-objects) — exact,
135 globally consistent counters when approximate isn't enough.
136- [RateLimit API reference](/providers/cloudflare/workers/ratelimit) —
137 props and client types.