---
title: "Cloudflare Workers"
url: "https://effect-auth.itsbroly.com/runtimes/cloudflare-workers/"
description: "Run effect-auth on the Cloudflare Workers runtime."
---



Cloudflare Workers run ES modules in V8 isolates and expose web-standard `Request`, `Response`, streams, and Web Crypto APIs. An Effect Auth deployment has three distinct parts:

```text
request -> Worker fetch -> Effect web handler -> auth services
                         |-> D1 binding (persistent auth records)
                         |-> Durable Object binding (rate-limit coordination)
                         `-> Send Email binding (delivery)
deployment tool (Wrangler or Alchemy) -> creates and binds those resources
```

| Concern                                   | Owner                                             |
| ----------------------------------------- | ------------------------------------------------- |
| HTTP entrypoint, `env`, isolate lifecycle | Cloudflare Workers runtime                        |
| Tables, migrations, queries               | D1 plus an Effect Auth storage adapter            |
| Resource creation and deployment          | Wrangler, Alchemy, or another infrastructure tool |

Alchemy is therefore optional infrastructure, not part of the runtime adapter. See [Alchemy infrastructure](/infrastructure/alchemy/) for its resource declarations and [D1 storage](/storage/cloudflare-d1/) for database setup. The [Quick Start](/quick-start/) combines all three concerns into one deployable application.

## Worker entrypoint [#worker-entrypoint]

Cloudflare's current [module Worker syntax](https://developers.cloudflare.com/workers/runtime-apis/handlers/fetch/) exports an object with `fetch(request, env, ctx)`. Bindings arrive on `env`; declare their types explicitly and pass them into the app-owned Effect layer factory:

```ts
import { HttpRouter } from "effect/unstable/http";

interface Env {
  readonly DB: D1Database;
  readonly RATE_LIMITER: DurableObjectNamespace;
  readonly AUTH_EMAIL: { send(message: unknown): Promise<unknown> };
  readonly AUTH_SECRET: string;
  readonly AUTH_ALLOWED_ORIGINS: string;
}

export default {
  fetch(request: Request, env: Env): Promise<Response> {
    const webHandler = HttpRouter.toWebHandler(makeAuthLayer(env), {
      disableLogger: true,
    });

    return webHandler.handler(request);
  },
} satisfies ExportedHandler<Env>;
```

`makeAuthLayer` is the composition shown in the [Quick Start](/quick-start/#build-the-worker): it supplies `D1EffectQbSqliteAuthStorageLive(env.DB)`, `WebCryptoLive()`, the mail binding, the Durable Object rate-limit store, secrets, selected features, and `HttpServer.layerServices`. The current Effect API returns an object from `HttpRouter.toWebHandler`; call `.handler(request)`, not the object itself.

The example constructs the handler per request, which guarantees that changed bindings and secrets are observed. Cloudflare may reuse an isolate for concurrent and later requests, but may evict it at any time; mutable global memory is neither durable nor globally shared. A handler may be cached only as an optimization when its captured `env` remains valid. The repository's TanStack example caches a handler because it imports a stable runtime `env`; a raw Worker should prefer the straightforward per-request form unless profiling justifies an env-aware cache. Never use handler memory for sessions, rate limits, or locks.

## Bindings and compatibility [#bindings-and-compatibility]

[Bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/) are capability-bearing APIs, not ordinary credentials. Typical Effect Auth bindings are:

* `DB`: a [D1 binding](https://developers.cloudflare.com/d1/worker-api/) consumed by the selected SQLite storage layer. Apply the migrations for the features you enable; D1 is persistence, not the Worker runtime itself.
* `RATE_LIMITER`: a [Durable Object namespace](https://developers.cloudflare.com/durable-objects/get-started/). The adapter selects object instances by rate-limit key, providing coordination that isolate-local maps cannot provide. The exported class and its migration/resource declaration remain app-owned.
* `AUTH_EMAIL`: a `send_email` binding. The repo currently uses `CloudflareMailer.layer` with the supported legacy `EmailMessage` API; Cloudflare's current [Workers email API](https://developers.cloudflare.com/email-service/api/send-emails/workers-api/) also documents a structured message builder for new integrations.
* Secrets and text configuration: `AUTH_SECRET`, public URL, cookie policy, and the exact allowed-origin list. Store secrets as Cloudflare secrets rather than plaintext configuration.

The examples enable `nodejs_compat`. Cloudflare documents that this flag, with a compatibility date of at least `2024-09-23`, enables supported Node APIs and injected shims. Shims do not make every Node method functional, so keep runtime code on web APIs where possible. Effect Auth uses `WebCryptoLive()` over the Worker's global `crypto`; Cloudflare documents the Web Crypto surface separately from Node `crypto`.

## Origins and lifecycle [#origins-and-lifecycle]

Set `AuthHttpApiConfigLive({ originCheck: { allowedOrigins } })` from an explicit allowlist. This server-side origin check protects state-changing auth requests; it is not a replacement for browser [CORS response and preflight headers](https://developers.cloudflare.com/workers/examples/cors-header-proxy/). If the browser calls auth cross-origin, configure both deliberately, return the requesting allowed origin rather than `*` when credentials are used, and include `Vary: Origin`.

Authentication work required for the response, including persistence and sending a requested auth email, should be awaited by the Effect handler. Use [`ctx.waitUntil`](https://developers.cloudflare.com/workers/runtime-apis/context/#waituntil) only for non-critical post-response work such as cache writes; queues are a better fit for work requiring reliable retries.

Finally, design against the current [Workers limits](https://developers.cloudflare.com/workers/platform/limits/): CPU, memory, subrequests, bundle size, and startup limits vary by plan. Waiting on D1 or network I/O is not CPU time, but expensive global initialization counts toward startup. These APIs and links were checked against the current Cloudflare documentation; pin a compatibility date and review its changes when upgrading.

