Node.js (Sentry-compatible SDK)
Use the official Sentry SDK for Node.js with a Belvador DSN and the data-scrubbing hook Belvador provides.
On this page
For Node.js servers Belvador uses the official Sentry SDK for Node.js with your Belvador DSN. Belvador adds the init options below and its own scrubbing from @belvador/core/sentry — the code Belvador's SDKs run — which strips request bodies, cookies and credentials before anything leaves your server.
Before you start#
A project with platform Node.js and its DSN.
A Node.js version supported by the current
@sentry/noderelease (see Sentry's Node.js guide).
1. Install#
npm install @sentry/node@11.9.0 @belvador/core@0.6.1@belvador/core is installed only for its stable @belvador/core/sentry entry: Belvador's scrubbing and captureResult for an official Sentry SDK. It loads no Sentry code of its own.
2. Create instrument.mjs#
import * as Sentry from "@sentry/node";import { scrubBreadcrumb, scrubEvent } from "@belvador/core/sentry";Sentry.init({ dsn: process.env.BELVADOR_DSN ?? "https://<public-key>@ingest.belvador.dev/<project-id>", release: process.env.APP_VERSION, environment: process.env.NODE_ENV, sendDefaultPii: false, tracesSampleRate: 0, maxBreadcrumbs: 50, maxValueLength: 8192, // Belvador's scrubbing: request bodies, cookies and credentials never leave the app. beforeSend: scrubEvent, beforeBreadcrumb: scrubBreadcrumb,});What the options do:
| Option | Why |
|---|---|
dsn | Reads BELVADOR_DSN from the environment, with the project DSN as fallback |
release, environment | Your app's version and environment, from APP_VERSION and NODE_ENV |
sendDefaultPii: false | No IP addresses, cookies or user details added by the SDK |
tracesSampleRate: 0 | The Sentry SDK sends no spans; for traces use OpenTelemetry |
maxBreadcrumbs: 50, maxValueLength: 8192 | Same bounds as Belvador's own SDKs |
beforeSend: scrubEvent | Removes request bodies, cookies, auth headers and client IP headers; masks values under sensitive names and credentials in text |
beforeBreadcrumb: scrubBreadcrumb | The same scrubbing for breadcrumbs (URLs with tokens, logged values) |
3. Load it before your app#
node --import ./instrument.mjs server.jsThe --import flag loads the instrumentation first in an ES module app. In a CommonJS app, require("./instrument") on the very first line of your entry file instead (rename the file to instrument.js and use require there).
Web frameworks#
@sentry/node instruments common frameworks. Express, Fastify, Koa and others need one extra line to report errors from their error handler — follow the framework section of Sentry's Node.js guide and keep the init options above. Belvador has not verified each framework.
Report errors you handle#
import * as Sentry from "@sentry/node";try { await chargeCard(order);} catch (error) { Sentry.captureException(error); throw new PaymentFailed(order.id);}Database errors that do not throw#
Supabase and PostgREST return { data, error } instead of throwing. Bind captureResult to your Sentry SDK once:
// captureResult on the app's own Sentry SDK (@sentry/node, @sentry/bun or @sentry/deno).import * as Sentry from "@sentry/node";import { captureResultWith } from "@belvador/core/sentry";export const captureResult = captureResultWith(Sentry);Then wrap each call; the result comes back unchanged and the error is reported typed by its Postgres code. The types and options: Database errors.
On a VPS: keep errors across crashes#
Without help, @sentry/node keeps unsent errors in memory: a crash, a restart or a deploy while your node is unreachable loses them. Add @belvador/spool and use this instrument.mjs instead:
import * as Sentry from "@sentry/node";import { scrubBreadcrumb, scrubEvent } from "@belvador/core/sentry";import { withSpool } from "@belvador/spool";Sentry.init({ dsn: process.env.BELVADOR_DSN ?? "https://<public-key>@ingest.belvador.dev/<project-id>", release: process.env.APP_VERSION, environment: process.env.NODE_ENV, sendDefaultPii: false, tracesSampleRate: 0, maxBreadcrumbs: 50, maxValueLength: 8192, // Belvador's scrubbing: request bodies, cookies and credentials never leave the app. beforeSend: scrubEvent, beforeBreadcrumb: scrubBreadcrumb, // Errors that cannot be sent wait on disk and go out after a restart. transport: withSpool(Sentry.makeNodeTransport, { dir: process.env.BELVADOR_SPOOL_DIR ?? "/var/lib/my-app/belvador", }),});Point
BELVADOR_SPOOL_DIRat a directory that survives restarts and that only your app's user can read — with systemd,StateDirectory=my-appgives you/var/lib/my-app.Errors wait there (at most 64 MiB, 72 hours) and are sent by the next process once your node answers. Tested: errors made while the node was unreachable arrived after the process was killed with
SIGKILL, and after it died of out-of-memory.An out-of-memory crash itself is not reported — the process dies before any code can run — but every error it had already kept is.
Details: Offline and durable delivery.
Set it up with an AI agent#
Paste this into Claude Code, Cursor or another coding agent. It contains the files above for a server that keeps errors across crashes, and the rules it must follow:
Belvador errors: Node.js (@sentry/node)Tested with: @sentry/* 11.9.0Guide: https://belvador.dev/en/docs/sdk/nodeGoalReport this server's errors to Belvador with the official Sentry SDK and Belvador's scrubbing, exactly as in the files below. Keep any other error SDK out: one Sentry.init per process.RulesCopy the files as they are and keep every Sentry.init option: sendDefaultPii: false, tracesSampleRate: 0, maxBreadcrumbs: 50, beforeSend: scrubEvent, beforeBreadcrumb: scrubBreadcrumb. Read the DSN from BELVADOR_DSN; do not invent other environment variables. Do not change authentication, routing or business logic. Report errors the code already handles with captureException (report() on Supabase Edge Functions) and wrap Supabase or PostgREST results that return an error instead of throwing with captureResult, without changing what the code does with the result.```bashnpm install @sentry/node@11.9.0 @belvador/core@0.6.1``````bashnpm install @belvador/spool@0.6.1```instrument.mjs```javascriptimport * as Sentry from "@sentry/node";import { scrubBreadcrumb, scrubEvent } from "@belvador/core/sentry";import { withSpool } from "@belvador/spool";Sentry.init({ dsn: process.env.BELVADOR_DSN ?? "https://<public-key>@ingest.belvador.dev/<project-id>", release: process.env.APP_VERSION, environment: process.env.NODE_ENV, sendDefaultPii: false, tracesSampleRate: 0, maxBreadcrumbs: 50, maxValueLength: 8192, // Belvador's scrubbing: request bodies, cookies and credentials never leave the app. beforeSend: scrubEvent, beforeBreadcrumb: scrubBreadcrumb, // Errors that cannot be sent wait on disk and go out after a restart. transport: withSpool(Sentry.makeNodeTransport, { dir: process.env.BELVADOR_SPOOL_DIR ?? "/var/lib/my-app/belvador", }),});```db.mjs```javascript// captureResult on the app's own Sentry SDK (@sentry/node, @sentry/bun or @sentry/deno).import * as Sentry from "@sentry/node";import { captureResultWith } from "@belvador/core/sentry";export const captureResult = captureResultWith(Sentry);```VerifyStart the app locally with BELVADOR_DSN set and trigger one test error (a route or a one-off script that throws). Confirm it appears as an issue in the Belvador project. Do not send test errors to production unless the user says so.ReportList the files you changed, the packages added with exact versions, the environment variables the app now needs, the test error you saw arrive, and anything you could not verify.Check that it works#
Run a one-off script with the same instrumentation:
import * as Sentry from "@sentry/node";Sentry.captureException(new Error("Belvador Node.js test"));await Sentry.flush(2000);The issue appears in the project within a few seconds.
Limits to know#
Belvador's scrubbing is pattern-based. A secret under an unusual name, or inside a free-text message, can slip through.
Your node stores error events, attachments, check-ins and Sentry logs, and counts delivery reports. Keep
tracesSampleRate: 0; tracing data and release-health sessions are counted as Not supported yet in the project's intake numbers and dropped.@sentry/node11 sends request sessions andSentry.loggercalls by default, so expect a small Not supported yet count.Scheduled jobs and workers can report runs with
Sentry.withMonitor— see Monitors with the official Sentry SDKs.Windows services: the same recipe applies, but Belvador has not run it on Windows yet.
Posts on this topic
All posts- Secrets in exception messages: what the Node SDK sends by defaultAPI keys, tokens and webhook URLs end up in error messages. We tested 15 real cases with @sentry/node defaults and two scrubbers: what leaked, and why.
- JavaScript error handling for AI-generated apps: where errors escapeWhere JavaScript errors escape in React and Node.js: a lab with 13 failure cases, three runs each, and the few lines that make each one visible.
- Sentry SDK migration checklist: test every assumptionMoving off Sentry to a compatible backend? A checklist built from a lab run: which items the Node.js SDK sends, which get dropped, and what to check.
Spotted something wrong or missing? Tell us: hello@belvador.dev