# Cloudflare Workers

> Wrap your Worker with withBelvador so every invocation gets its own scope and errors are sent while responses stay unchanged.

`@belvador/cloudflare` wraps a Cloudflare Worker so errors in its handlers are reported while responses stay exactly as your code returns them. For TanStack Start or Lovable apps use [TanStack Start](https://belvador.dev/en/docs/sdk/tanstack-start), which includes this.

## Before you start

- A project and its DSN. There is no Workers platform in the project list; choose **Other** — the platform only selects the setup text shown in the app.
- A Worker in ES module syntax (`export default { fetch … }`) deployed with Wrangler.
- The **`nodejs_compat`** compatibility flag. It gives every invocation its own scope, so user, tags and context never leak between requests.

## 1. Install

```bash
npm install @belvador/cloudflare@0.6.1
```

## 2. Enable `nodejs_compat`

In `wrangler.jsonc` (or the matching key in `wrangler.toml`):

```jsonc title="wrangler.jsonc"
{
  "compatibility_flags": ["nodejs_compat"]
}
```

## 3. Wrap the Worker

```ts title="src/index.ts"
import { withBelvador } from "@belvador/cloudflare";

type Env = { BELVADOR_DSN?: string; BELVADOR_RELEASE?: string };

// Requires the nodejs_compat compatibility flag.
// The DSN comes from env.BELVADOR_DSN when you leave it out here.
export default withBelvador(
  (env: Env) => ({
    dsn: env.BELVADOR_DSN ?? "https://<public-key>@ingest.belvador.dev/<project-id>",
    release: env.BELVADOR_RELEASE,
  }),
  {
    async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
      return new Response("ok");
    },
  },
);
```

Keep the DSN out of the code if you prefer — set it as a Worker secret and the SDK reads it:

```bash
npx wrangler secret put BELVADOR_DSN
```

| Variable | Used for | Default |
|---|---|---|
| `BELVADOR_DSN` | The project DSN, when the options callback does not return `dsn` | — (SDK stays off) |
| `BELVADOR_RELEASE` | Your deploy's version | `CF_VERSION_METADATA.id` when that binding exists |
| `BELVADOR_ENVIRONMENT` | `production`, `staging`, … | `production` |
| `BELVADOR_DISABLED` | `1` or `true` turns sending off | — |

The SDK never reads `SENTRY_*` variables.

## What is reported

`withBelvador` wraps the handlers on the exported object: `fetch`, `scheduled`, `queue`, `email` and `tail`.

- Each invocation gets its own scope.
- An error thrown by your handler is reported, then **re-thrown** — Cloudflare sees the same failure it would without Belvador.
- Your response (status, headers, streamed body) is returned unchanged.
- A cancelled request is not reported (from 0.2.0): an `AbortError`, or an error thrown after the client disconnected. Set [`captureAborted: true`](https://belvador.dev/en/docs/sdk/configuration#common-options) to report it anyway.
- A promise you pass to `ctx.waitUntil` that rejects is reported as an unhandled error with the request's scope (from 0.4.1), then handed to Cloudflare unchanged, so Workers Logs still show it. It has to fail within about 2 seconds after your handler returns, while the SDK is still sending; later failures, and promises you never pass to `waitUntil`, are not seen.
- Sending finishes after the response, in `ctx.waitUntil`, so it does not delay the user.

Other entry points, such as Durable Object classes, are not wrapped in 0.x.

## Report errors you handle

```ts
import { captureException, setTag, withBelvador } from "@belvador/cloudflare";

export default withBelvador(() => ({}), {
  async fetch(request: Request): Promise<Response> {
    setTag("route", new URL(request.url).pathname);
    try {
      return await handle(request);
    } catch (error) {
      captureException(error);
      return new Response("Something went wrong", { status: 502 });
    }
  },
});
```

The tag above keeps only the path; the SDK also masks credentials it recognises in URLs and query strings.

## Breadcrumbs

Console and `fetch` breadcrumbs are off by default. Turn them on in the options callback:

```ts
withBelvador((env: { BELVADOR_RELEASE?: string }) => ({
  release: env.BELVADOR_RELEASE,
  instrument: { console: true, fetch: true },
}), worker);
```

## Check that it works

Deploy a temporary check inside the handler wrapped by `withBelvador`, call it once, and read the result back (or open the project's **Connection** tab):

```ts title="src/index.ts"
import { verifyIntegration } from "@belvador/cloudflare";

// Inside the fetch handler wrapped by withBelvador. Temporary: remove after the check.
if (new URL(request.url).pathname === "/__belvador/verify") {
  return Response.json(await verifyIntegration());
}
```

```bash
curl -s "https://ingest.belvador.dev/api/<project-id>/verification/bv1_<id printed by your app>/?sentry_key=<public-key>"
```

Remove the check afterwards — see [Verify the connection](https://belvador.dev/en/docs/sdk/verify).
