Cloudflare Workers
Wrap your Worker with withBelvador so every invocation gets its own scope and errors are sent while responses stay unchanged.
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_compatcompatibility flag. It gives every invocation its own scope, so user, tags and context never leak between requests.
1. Install#
npm install @belvador/cloudflare@0.6.12. Enable nodejs_compat#
In wrangler.jsonc (or the matching key in wrangler.toml):
{ "compatibility_flags": ["nodejs_compat"]}3. Wrap the Worker#
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:
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. SetcaptureAborted: trueto report it anyway.A promise you pass to
ctx.waitUntilthat 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 towaitUntil, 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#
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:
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):
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());}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 postsSpotted something wrong or missing? Tell us: hello@belvador.dev