Skip to content

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.

  • Wiring. Your code says env.DB. Your config says there’s a database bound as DB. Nothing checks that those two agree until a request fails in production with Cannot 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.

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:

  1. 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.
  2. 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.
  3. 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.

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.

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.