Skip to content

Cloudflare Workers

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

Markdown
On this page

@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, 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#

Terminal
npm install @belvador/cloudflare@0.6.1

2. Enable nodejs_compat#

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

wrangler.jsonc
{  "compatibility_flags": ["nodejs_compat"]}

3. Wrap the Worker#

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:

Terminal
npx wrangler secret put BELVADOR_DSN
VariableUsed forDefault
BELVADOR_DSNThe project DSN, when the options callback does not return dsn— (SDK stays off)
BELVADOR_RELEASEYour deploy's versionCF_VERSION_METADATA.id when that binding exists
BELVADOR_ENVIRONMENTproduction, staging, …production
BELVADOR_DISABLED1 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 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#

TypeScript
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.

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

TypeScript
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):

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());}
Terminal
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.

Posts on this topic

All posts

Spotted something wrong or missing? Tell us: hello@belvador.dev