JavaScript in the browser
Install @belvador/browser for websites and browser apps that do not use React.
On this page
Use @belvador/browser for browser JavaScript that does not use React — plain TypeScript, a site built with Vite, a widget. For React use React; for Lovable or TanStack Start use TanStack Start.
Before you start#
A project with platform JavaScript (browser) and its DSN — see Projects and DSN.
A bundler that handles ES modules (Vite, esbuild, Rollup, webpack 5).
1. Install#
npm install @belvador/browser@0.6.12. Initialize#
Put init in its own file, src/instrument.ts:
import { init } from "@belvador/browser";init({ dsn: "https://<public-key>@ingest.belvador.dev/<project-id>", release: import.meta.env.VITE_APP_VERSION, // your build or commit id environment: import.meta.env.MODE,});Then make it the first import of your entry file. Imports run before the rest of a file, so an init call written below your other imports never runs when one of those modules fails while loading — a missing environment variable, for example — and the error is never reported.
import "./instrument"; // first line: starts Belvador before your app's modules load// …the rest of your imports and start-up codereleaseis your app's version, such as a build number or commit id; it lets Belvador tell which deploy an error came from and match source maps. SetVITE_APP_VERSIONin your build, or pass any string.environmentseparates production from staging. It defaults toproductionwhen left out.
init returns true when error tracking is running. It never throws: an invalid DSN logs one warning in the console and leaves the SDK switched off. Calling init again with the same DSN does nothing.
3. Allow the ingest address in your CSP#
If your site sends a Content-Security-Policy header, add the ingest origin to connect-src. Merge it into your existing policy — do not replace the policy:
connect-src 'self' https://ingest.belvador.dev;Without it the browser blocks the request and errors never arrive.
What is captured#
As soon as init has run:
uncaught errors (
window.onerror) andunhandled promise rejections.
Errors from browser extensions are ignored. Breadcrumbs from the console, clicks, fetch, XHR and navigation are off by default; turn them on with the instrument option (SDK options).
Frameworks that catch errors themselves (for example Vue's app.config.errorHandler or Angular's ErrorHandler) keep those errors away from window.onerror. Call captureException from that hook to report them.
Report errors you handle#
When your code catches an error and recovers, the SDK does not see it. Report it yourself when you still want to know:
import { captureException } from "@belvador/browser";async function saveOrder(order: Order) { try { await api.post("/orders", order); } catch (error) { captureException(error, { tags: { feature: "checkout" } }); showToast("We couldn't save your order. Please try again."); }}captureException returns an event ID. The ID is created in your app; it does not prove the event reached your node.
Add context#
import { setContext, setTag, setUser } from "@belvador/browser";setUser({ id: account.id }); // only the id is keptsetTag("plan", account.plan);setContext("cart", { items: cart.length });Do not put emails, names or tokens in tags or context: they are shown to everyone in the workspace. The SDK masks common secret patterns, but that is a safety net, not a filter you can rely on — see Data protection.
Check that it works#
Deploy or run the app with the setup above.
Trigger a test error, for example from a button:
setTimeout(() => { throw new Error("Belvador browser test");}, 0);Open the project in Belvador: the issue appears within a few seconds. If not, open your browser's network tab and look for a
POSTtohttps://ingest.belvador.dev/api/…/envelope/— Troubleshooting explains each status code.
Limits to know#
Errors from scripts on another domain show up as
Script error.without details unless that script is served with CORS headers and loaded withcrossorigin="anonymous"— a browser rule, not a Belvador one.Events wait in memory only. If the page closes before they are sent, they are lost.
Stack traces of minified code need source maps to show your original file and line.
Posts on this topic
All posts- Unhandled promise rejections vs caught errors: what your tracker receives14 browser failures run through an error tracker: which arrive, which vanish when caught and logged, and why rejecting with a string loses the stack.
- React error boundaries and async errors: what our lab caughtWhich errors a React 19 error boundary catches, which reach onCaughtError, onUncaughtError or window, and why standalone startTransition differed.
- 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.
Spotted something wrong or missing? Tell us: hello@belvador.dev