JavaScript / TypeScript SDK
Report errors to BugBoard in three steps: install, copy one setup file, then call it.
Source on GitHub: github.com/bug-board/bugboard-js Runs on Node 20+, browsers, and edge runtimes with nothing to install or wire up (it uses the platform
fetch). The default path loads no dependencies; opt-in payload encryption lazy-loads a tiny tweetnacl sealed-box binding (tweetnacl-sealedbox-js) that ships bundled with the SDK — it loads automatically when you set an encryption key, with nothing extra to install. See Encrypting sensitive reports.
1. Install
npm i bugboard
2. Add the setup file
Create one file that configures a shared client, then import it anywhere. This keeps your keys in a single place. Pick the tab for where the client runs:
// utils/bugboard.ts import { createClient } from 'bugboard'; // In the browser or a mobile app, use a publishable key, sent as a bearer token. export default createClient({ apiKey: import.meta.env.VITE_BUGBOARD_API_KEY, // bb_pub_… // OPTIONAL — if provided, every payload is encrypted before it leaves the client. // encryptionKeyId: import.meta.env.VITE_BUGBOARD_ENCRYPTION_KEY_ID, // bbek_… // encryptionPublicKey: import.meta.env.VITE_BUGBOARD_ENCRYPTION_PUBLIC_KEY, // base64 X25519 });
// utils/bugboard.ts import { createClient } from 'bugboard'; // On a server, use a secret key; requests are HMAC-signed and the secret never leaves the server. export default createClient({ keyId: process.env.BUGBOARD_KEY_ID, // bbk_… signingSecret: process.env.BUGBOARD_SIGNING_SECRET, // bb_sec_… // OPTIONAL — if provided, every payload is encrypted before it leaves the server. // encryptionKeyId: process.env.BUGBOARD_ENCRYPTION_KEY_ID, // bbek_… // encryptionPublicKey: process.env.BUGBOARD_ENCRYPTION_PUBLIC_KEY, // base64 X25519 // OPTIONAL — log reports locally instead of sending them (local debugging / dry run). // logLocally: true, });
Set the matching values in your .env:
VITE_BUGBOARD_API_KEY=bb_pub_xxxxxxxx
BUGBOARD_KEY_ID=bbk_xxxxxxxx BUGBOARD_SIGNING_SECRET=bb_sec_xxxxxxxx
3. Use it
Call a severity method with a title (required); optionally pass a description — a string or the caught error — and tags.
import bugboard from '@/utils/bugboard'; try { await payments.charge(order); } catch (err) { bugboard.criticalHigh('Payment failed', err, ['payment', 'backend']); }
The title is all you need:
bugboard.major('Checkout is slow');
That's it — the call returns immediately and the report is delivered in the background.
Common methods
Most apps only need these four (medium priority); each severity also has …Low and …High
variants — criticalHigh, minorLow, and so on:
bugboard.critical('Payment failed', err); // highest severity bugboard.major('Checkout is slow', err); bugboard.moderate('Slow image upload', err); bugboard.minor('Tooltip misaligned');
Tags are an array or a comma-separated string:
bugboard.critical('Payment failed', err, ['payments', 'checkout']); bugboard.critical('Payment failed', err, 'payments,checkout');
Where cards point
Every card records the code it is about. Pass the caught error as the description and it names where that error was thrown, not where you reported it:
bugboard.criticalHigh(err.message, err); // → src/checkout/charge.ts bugboard.major('Cart total went negative'); // → wherever you called from
That is what makes one global handler workable: without it, every error in the app would report the file your handler lives in.
Bundled apps need the build plugin
Once a bundler minifies your code, the call stack only names a chunk — cards read
assets/index-a3f2b1.js. Adding the build plugin writes the real file and line in at compile time,
with no source maps generated, deployed, or uploaded:
// vite.config.ts import bugboard from 'bugboard/vite'; export default { plugins: [bugboard()] };
bugboard/rollup is the same plugin; bugboard/webpack is a loader (and works under Turbopack);
bugboard/esbuild covers Lambda, Wrangler and SST bundles. JSX/TSX and Vue/Svelte single-file
components are all handled.
For a bundled Node server, add import 'bugboard/preload' as the very first import instead —
it switches on Node's own source-map support, which reads the .js.map files already sitting next
to your chunks.
Line numbers are opt-in
Cards carry file_name by default but not line_number. The file is dependable everywhere;
the line is only exact once the build plugin writes it in, and otherwise it is inferred from the
call stack and can point at the wrong statement. A line that is almost right reads as authoritative
and sends people to the wrong place, so you switch it on deliberately:
createClient({ apiKey: '…', lineNumbers: true });
Install the plugin and turn this on in the same commit — that is the combination that makes both fields exact in production.
Full reference: SOURCE_LOCATIONS.md covers every mechanism, all the plugin options, setup per bundler and framework, and how to turn any of it off (
captureLocation: false,sourceMaps: false).
Serverless / short-lived scripts
Reports deliver in the background, and the SDK flushes automatically on shutdown (Node
beforeExit, browser pagehide). In environments without lifecycle hooks — lambdas, edge
functions, CLI scripts — flush before returning:
await bugboard.flush();
Framework guides
The SDK is identical on every runtime — what changes is where you build the client, which key it uses, and where you flush. These guides cover each one end to end:
| Server | Full-stack | Client |
|---|---|---|
| Express, Fastify, Koa | Next.js | Vite SPA: React, Vue, Svelte |
| NestJS | Nuxt | |
| Serverless & edge | SvelteKit | |
| Remix / React Router |
In an app with both a server and a client bundle, make two modules — one with the secret
key that never reaches the browser, one with the publishable key that does. Adopt whatever
server-only convention your framework enforces (.server.ts in SvelteKit and Remix,
server-only in Next.js) so an accidental client import fails at build time rather than
shipping your signing secret.
Next steps
- Framework guides — detailed integration for every major framework.
- API Reference — config options, every severity/priority variant
(
criticalHigh, …), and the full HTTP contract. - Source locations — the build plugin, source maps, and line numbers in full.
- Encrypting sensitive reports — keep the payload out of the
browser network tab with
encryptionPublicKey(the bundledtweetnacl-sealedbox-jsbinding auto-loads for you). - Source on GitHub — the full SDK implementation.