Cloudflare Workers
The Cloudflare adapter exports a Workers module entrypoint: the canonical export default { fetch } shape. Service Worker style (addEventListener("fetch", ...)) is no longer recommended. The adapter does not emit it.
- platformWorker fetch(request, env, ctx)export default { fetch }
- adaptertoFetchHandler(app)bindings via cloudflare:workers env
- coreapp.fetch(Request)routing · hooks · handler
- platformResponse
When to choose Workers
- You want global, low-latency execution without managing regions yourself.
- You can live without raw TCP sockets (Workers Hyperdrive solves Postgres).
- You want bindings (KV, R2, D1, Durable Objects, Queues) instead of standalone services.
Scaffold
Worker entrypoint (no bindings)
If you don't need env bindings or the Worker ExecutionContext, toFetchHandler is a one-liner. It returns the { fetch } object Workers expect as the default export, so do not wrap it again.
Workers have no NODE_ENV, so set production mode explicitly. If you write src/server.ts by hand instead of scaffolding with create-daloy, configure the App the way the Cloudflare template does:
Without production: true, the production-only refuse-to-boot guards do not refuse; each would-be refusal is only logged as a secure_defaults.env_indeterminate warning. 5xx detail is still redacted, because an unset environment fails closed. Without behindProxy, production requests carrying X-Forwarded-For are refused with a 500. Fix that with { hops: 1 }, not "none" or trustProxy: a Worker has no TCP peer, so those settings make rateLimit() put every caller in one shared bucket. DaloyJS logs a rate-limit.shared-bucket warning in production when that happens.
wrangler.jsonc
Cloudflare now recommends wrangler.jsonc over wrangler.toml for new projects. Both are still supported. The single nodejs_compat flag is all you need on a recent compatibility date, there's no separate nodejs_compat_v2 to add.
Deploy
wrangler publish was renamed to wrangler deploy in 2024. Do not use the old name. Some CI templates still reference it.
Bindings (env)
toFetchHandler(app) only forwards the Request. To expose Worker bindings (KV, R2, D1, Durable Objects, Queues, Hyperdrive, secrets) to your handlers, import env from cloudflare:workers and pass it to app.decorate(...) before you register routes. That's how DaloyJS makes runtime values available on ctx.state inside every handler.
Do not call app.decorate() inside the Worker fetch handler. It throws on the second request, and a first decoration added after the routes were registered never reaches them. The imported env can be read at module scope, but binding I/O (a KV read, a D1 query) still has to happen inside a request, which is where your handlers run.
Inside any route handler, read the binding from ctx.state.env (the key you passed to decorate):
Gotchas
- Workers have no raw TCP. Use Hyperdrive for Postgres/MySQL, or HTTP drivers like Neon's serverless driver, PlanetScale's
@planetscale/database, or Turso/libSQL. See Database hosting. - Workers have no filesystem. Use multipart uploads with R2 rather than
node:fs. - For background work, import
waitUntilfromcloudflare:workersand call it inside the handler:toFetchHandlerdoes not forward the WorkerExecutionContextto your routes. (It does usectx.waitUntilitself to flush telemetry.)