Why this exists
Cloudflare gives you an astonishing toolbox: Workers that start in milliseconds all over the world, KV, D1, R2, queues, Durable Objects, Workflows, AI models, vector search, browsers, all a binding away. Writing one Worker against it is a joy.
Then the app grows, and the hard parts show up.
The hard parts
Section titled “The hard parts”- Wiring. Your code says
env.DB. Your config says there’s a database bound asDB. Nothing checks that those two agree until a request fails in production withCannot read properties of undefined. - Deploys that half-happen. A deploy makes a queue, then a database, then fails uploading the Worker. Now you have a queue and a database nobody uses, and a Worker that isn’t there. Press Ctrl-C at the wrong moment and it’s the same, only worse.
- Calls between Workers. Service bindings and RPC are fast and native, but the shape of what you send is a promise between two codebases, and nothing holds either side to it, especially mid-rollout, when old callers meet a new callee.
- Environments. A preview per pull request is wonderful until you’re naming every resource by hand and cleaning up the ones a closed PR left behind.
- Tests. Testing a handler that touches D1, a queue and a Durable Object usually means mocks that drift from the real thing.
What Effect changes
Section titled “What Effect changes”Effect is a TypeScript library for programs that do real-world things: they fail in typed ways, need services, run concurrently and clean up after themselves. Three of its ideas line up almost perfectly with Cloudflare’s hard parts:
- An Effect’s type lists what it needs. A handler that reads a database has that database in its type. So if the database’s service is also its declaration, the compiler can check a Worker binds everything its code uses. Wiring mistakes become type errors.
- Work can be all or nothing. Effect’s scopes and finalizers make
“undo everything if any step fails, or if I’m interrupted” a natural way
to write a deploy, not a pile of
try/catch. - Services are swappable. A handler asks for
Book; production answers with D1, a test answers with SQLite in memory. No mocks, just a different Layer.
forge-effect is what you get when you build on those three.
The layers
Section titled “The layers”It comes in pieces, and each one works without the ones above it:
| Package | What it is |
|---|---|
@forge-effect/cloudflare |
An Effect client for Cloudflare’s whole API, generated from its OpenAPI document by a fork of Cloudflare’s own SDK generator. |
@forge-effect/ship |
A deploy engine, with no Wrangler inside: provisions what a Worker needs, uploads a version, rolls it out a step at a time behind smoke tests, and undoes everything on failure. It runs in Node, or inside a Worker. |
@forge-effect/deployer |
A Worker that runs ship, so deploys can happen inside Cloudflare. |
@forge-effect/infra |
Apps declared in Effect: resources that are their own clients, Workers the compiler checks, typed calls between them, and plan, ship and destroy for the whole thing. |
Most of this guide is about infra, because that’s where you’ll spend
your time. But if all you want is a safer deploy for the Worker you
already have, ship alone does that, from your existing
cloudflare.config.ts.
What we believe
Section titled “What we believe”A few beliefs shaped every decision:
- Strongly typed, checked at both ends. The compiler refuses what it can (a missing binding, a call that doesn’t match its contract); a runtime check catches what types can’t (an old version answering mid-rollout).
- Plans are data, deploys are all or nothing. Everything that changes
your account is planned first, and you can read the plan. A failed deploy
puts things back. Data is never deleted as a side effect of anything
else: tearing down production needs you to say
--allow-data-loss. - Native over reinvented. Calls between Workers use Cloudflare’s own RPC, not HTTP. Resources are Cloudflare’s own, by Cloudflare’s names. Your app stays an ordinary Cloudflare app.
- Mix with Cloudflare’s tools. Where forge-effect meets Cloudflare’s
tooling it speaks Cloudflare’s formats (
cloudflare.config.ts, the Build Output), so you can use both. - Errors that tell you what to do. Every problem has a stable code, where it is, and a fix, readable by you and by the coding agent next to you.
- Verified against the real thing. Tests with stand-ins are necessary, not sufficient: every feature here was also run in workerd, and on a real Cloudflare account, unless its page says otherwise.
Ready to see it? Build your first app. Or read how it thinks first, if you like the map before the trip.