Browser SDK
Capture exceptions, breadcrumbs, network activity, and user interactions from front-end applications. Beacon-safe flush, session controls, and storm suppression included.
Installation
npm install @debugbundle/sdk-browserQuick Setup
import DebugBundle from '@debugbundle/sdk-browser';
DebugBundle.init({
transportMode: 'relay',
endpoint: '/debugbundle/browser',
environment: 'production',
service: 'my-spa',
});Calling init() automatically:
- Hooks browser
windowerror and unhandled-rejection events - Registers
beforeunload/visibilitychangefor unload-safe flushing - Starts breadcrumb capture (clicks, route changes, console, network)
- Fetches direct-mode remote config from the API when a browser project token is configured
Configuration
| Option | Type | Default | Description |
|---|---|---|---|
projectToken | string | — | Required for direct API ingestion. Omit it when you send browser events through a same-origin relay that injects the project token server-side. Direct browser tokens are public write-only keys; create them with allowed browser origins from the dashboard or allowed_origins from API automation when using static-only deployment. |
environment | string | "production" | Environment name. |
service | string | document.title or "browser-app" | Service identifier. |
endpoint | string | derived from transport | Relay or ingestion endpoint. Set this to your relay path or backend relay URL when using relay transport. |
transportMode | 'relay' | 'direct' | inferred | Explicitly select relay or direct ingestion. Use relay for absolute backend relay URLs in split frontend/backend deployments. |
enabled | boolean | true | Kill switch. |
sampleRate | number (0–1) | 1.0 | Event-level sampling rate. |
logLevel | LogLevel | "warning" | Minimum log level to capture. |
redactFields | string[] | ["password", "secret", "token", "authorization", "cookie", "ssn", "credit_card"] | Field names to redact from captured data. |
maxBreadcrumbs | number | 10 | Maximum breadcrumbs retained in the ring buffer. |
breadcrumbsOnErrorOnly | boolean | true | When true, breadcrumbs attach only to error events instead of being shipped as standalone frontend_breadcrumb events. |
captureNetwork | boolean | true | Capture fetch and XMLHttpRequest calls as breadcrumbs; first-party promoted failures also emit request_event. |
tracePropagationTargets | string[] | same-origin only | Cross-origin first-party URL substrings allowed to receive X-DebugBundle-Trace-Id. |
captureClicks | boolean | true | Capture click events with CSS selector paths. |
captureRouteChanges | boolean | true | Capture popstate and pushState/replaceState route changes. |
captureConsole | boolean | false | Opt-in: capture console.error and console.warn. |
networkFilter | BrowserNetworkFilterConfig | — | Filter captured network requests by URL pattern, header, or status code. |
sessionSampleRate | number (0–1) | 1.0 | Session-level sampling. Decided once at session start. |
maxEventsPerSession | number | 100 | Hard cap per session. After this, only exceptions and first-party 5xx request events are captured. |
batchSize | number | 25 | Events per flush batch. |
flushInterval | number (ms) | 3000 | Auto-flush interval. |
maxBufferedEvents | number | 500 | Max events in buffer before oldest are dropped. |
requestTimeoutMs | number (ms) | 5000 | HTTP transport timeout. |
onDiagnostic | (diagnostic) => void | — | Callback for SDK self-diagnostic events. |
beforeSend | (event) => event | null | — | Synchronous final hook before buffering. Return null to drop locally. |
Use beforeSend for app-owned local policy such as final redaction or tenant-specific suppression before an event leaves the page. The hook runs after the SDK builds the event and before project capture rules, sampling, suppression, and transport. Use project capture rules first for known operational noise because they are centralized, auditable, and enforced by ingestion/worker backstops. Use networkFilter for network breadcrumb/request capture choices.
debugbundle.init({
transportMode: "relay",
endpoint: "/debugbundle/browser",
service: "checkout-web",
environment: "production",
beforeSend(event) {
if (event.event_type === "frontend_exception" && event.payload.message === "Expected local-only error") {
return null;
}
return event;
},
});If the hook throws or returns an invalid event, the SDK keeps the original event and swallows the hook failure so the host page keeps running.
Transport Resolution
The Browser SDK supports explicit relay and direct modes. Same-origin relay paths such as /debugbundle/browser are inferred as relay for compatibility. For split frontend/backend deployments, set transportMode: 'relay' and point endpoint at the backend relay URL, for example https://api.example.com/debugbundle/browser. Relay mode never sends Authorization or projectToken from the browser.
Direct mode requires a dedicated public write-only project token and an absolute ingestion endpoint. If you provide only projectToken, the SDK defaults to DebugBundle cloud ingestion.
The same-origin relay path is preferred because it avoids CORS, ad-blocker interference, and content security policy issues. Cross-origin relay works when the relay route answers CORS preflight and the relay handler is configured with explicit allowed origins.
If you have a static-only site with no backend relay, create a dedicated project token with allowed browser origins through the dashboard, CLI, API, or MCP. The dashboard field is Project → Tokens → Create project token → Allowed browser origins; enter one origin per line or comma-separated. API and automation clients use allowed_origins.
When allowed_origins is set, DebugBundle rejects direct POST /v1/events and GET /v1/sdk/config calls unless the request includes a matching Origin header. Server SDKs and backend relay forwards usually do not send Origin, so do not reuse an allowlisted browser token for those paths. The allowlist blocks normal browser requests from other origins, but it does not make the token secret because non-browser clients can spoof Origin.
Content Security Policy is a browser-side concern. If the Browser SDK sends directly to DebugBundle cloud or to a cross-origin relay, your CSP must allow the endpoint in connect-src. If you use a same-origin relay such as /debugbundle/browser, your CSP usually only needs to allow your own origin.
Opaque Browser Errors
Sometimes a bundle shows a Browser SDK frame such as onError with a message like Window error or Browser resource load error. That usually means the SDK observed a browser-level window error event, but the browser did not expose a normal JavaScript Error object or application stack.
Common causes:
- A script, stylesheet, image, or other resource failed to load.
- A cross-origin script threw without CORS and
crossoriginconfigured, so the browser hid the real stack. - Code dispatched a bare
errorevent or threw a non-Errorvalue. - The SDK was installed after a framework-level error boundary, so only the degraded global browser signal remained.
For these captures, DebugBundle includes payload.browser_event on the frontend_exception event. Use kind, file_name, line_number, column_number, and target.source_url to inspect the browser's best available clue. When opaque is true, do not treat the SDK listener frame as the application source.
Known operational patterns include CSP-blocked resources, cross-origin scripts without CORS plus crossorigin, browser extension/resource-policy failures, and third-party analytics tags that fail under bots or privacy controls. After confirming the pattern, prefer a server-side capture rule scoped by browser_event_kind, browser_event_opaque, message_equals, services, and environments.
To improve fidelity:
- Initialize the Browser SDK as early as possible, before application bundles run.
- Capture framework-native errors with
DebugBundle.captureException(error)from error boundaries or global framework hooks. - For cross-origin scripts, serve the script with CORS and use
crossorigin="anonymous"on the script tag. - Publish source maps for application bundles so real stacks can be symbolicated when the browser provides them.
Breadcrumbs
Breadcrumbs are a chronological trail of user actions and system events leading up to an error. They attach automatically to every captured exception.
Captured Types
| Type | Source | Example |
|---|---|---|
click | DOM click listener | button.submit-btn clicked |
navigation | popstate / History API | Navigated to /dashboard |
network | fetch / XMLHttpRequest | GET /api/users → 200 (142ms) |
console | console.error, console.warn | console.error("timeout") |
custom | DebugBundle.addBreadcrumb() | User-defined context events |
Manual Breadcrumbs
DebugBundle.addBreadcrumb({
type: 'custom',
category: 'checkout',
message: 'Payment form submitted',
data: { paymentMethod: 'card' },
});Ring Buffer
The SDK maintains a fixed-size ring buffer (default: 50). When the buffer is full, the oldest breadcrumb is dropped. On exception capture, the entire buffer attaches to the error event.
Network Capture
When captureNetwork is enabled, the SDK intercepts fetch and XMLHttpRequest to log:
- Method and URL
- Response status code
- Duration (ms)
- Request/response content length
Rejected fetch() calls that never receive an HTTP response are captured as failed network breadcrumbs with status_code: 0 plus failure_kind and failure_reason fields when available. This covers cases such as blocked domains, offline tabs, DNS failures, and CORS-level network errors without changing application behavior.
The fetch wrapper preserves normal browser request semantics. It supports string, URL, and Request inputs, and preserves caller headers supplied as Headers, tuple arrays, or records. When the SDK adds X-DebugBundle-Trace-Id, it adds the trace header to the effective header set without dropping existing headers such as Authorization.
Project capture rules can demote, sample, or drop known browser noise after you identify it from an incident. The Browser SDK applies active rules locally before upload when possible, so recurring third-party resource-load failures can become context-only or be discarded without opening new incidents.
Unhandled promise rejections are captured through the global unhandledrejection hook. When the browser exposes a reason, the SDK includes a bounded rejection_reason summary so generic incidents such as Unhandled promise rejection can distinguish Error, string, object, null, and undefined reasons.
Network Filtering
Use networkFilter to control what gets captured:
DebugBundle.init({
projectToken: 'tok_xxxx',
captureNetwork: true,
networkFilter: {
denyUrls: [/analytics\.example\.com/, /\/health$/],
allowUrls: [/\/api\//],
captureRequestHeaders: ['content-type', 'x-request-id'],
captureResponseHeaders: ['x-request-id'],
captureRequestBody: false,
captureResponseBody: false,
},
});| Filter Option | Type | Description |
|---|---|---|
denyUrls | (string | RegExp)[] | URLs matching any pattern are excluded. |
allowUrls | (string | RegExp)[] | If set, only matching URLs are captured. |
captureRequestHeaders | string[] | Allowlist of request headers to capture. |
captureResponseHeaders | string[] | Allowlist of response headers to capture. |
captureRequestBody | boolean | Capture request body (default: false). |
captureResponseBody | boolean | Capture response body (default: false). |
Sensitive headers (authorization, cookie) are always redacted regardless of allowlist.
Session Controls
Session Sampling
sessionSampleRate determines whether an entire session is captured. The decision is made once when the session starts and persists for that session, so you get either full context or nothing.
DebugBundle.init({
projectToken: 'tok_xxxx',
sessionSampleRate: 0.1, // Capture 10% of sessions
});Per-Session Event Cap
maxEventsPerSession prevents runaway event volume from a single user session:
DebugBundle.init({
projectToken: 'tok_xxxx',
maxEventsPerSession: 50, // Hard cap per session
});Session API
// Get current session ID
const sessionId = DebugBundle.getSessionId();
// Reset session (e.g., on logout)
DebugBundle.resetSession();Trace Correlation
The Browser SDK adds an X-DebugBundle-Trace-Id header to same-origin outgoing fetch and XMLHttpRequest requests. Your backend SDK reads this header to correlate frontend and backend events into a single incident timeline.
// Automatic for same-origin requests
// The header is added to all fetch requests to same-origin URLs
// To include cross-origin first-party APIs:
debugbundle.init({
transportMode: 'relay',
endpoint: 'https://api.example.com/debugbundle/browser',
tracePropagationTargets: ['https://api.example.com'],
});The relay endpoint controls where browser events are delivered. tracePropagationTargets controls which cross-origin application requests receive trace headers and can become first-party request-failure incident signals. Configure both for split frontend/backend apps when the API host differs from the web origin.
Handled HTTP responses do not need to throw an exception to become request incidents. A failed response can emit a standalone request_event when it is first-party for trace propagation and matches the active capture preset, immediate_client_error_statuses, or an immediate_client_error_path_rules entry. Unpromoted 4xx responses remain breadcrumbs/context and do not open incidents by themselves.
Device Context
Every event includes automatically collected device context from the browser runtime:
| Field | Example |
|---|---|
userAgent | Mozilla/5.0 ... Chrome/125.0.6422.142 Safari/537.36 |
browser | Chrome 125.0.6422.142 |
os | macOS 15.1 |
deviceType | desktop |
screen | 2560x1440 |
viewport | 1280x720 |
devicePixelRatio | 2 |
touchCapable | false |
language | en-US |
connectionType | 4g |
colorSchemePreference | dark |
This metadata improves incident triage, but it also increases the amount of browser-identifying context attached to each event. In combination, fields such as user agent, screen size, viewport, language, touch capability, connection type, and color-scheme preference can contribute to browser fingerprinting.
If your privacy posture does not allow that level of client metadata, do not deploy the Browser SDK on those surfaces. There is no field-level device-context toggle today; the safe boundary is to keep capture server-side only or avoid browser instrumentation for the affected application.
Manual Capture
Exceptions
try {
await riskyClientOperation();
} catch (error) {
DebugBundle.captureException(error, {
tags: { component: 'checkout-form' },
});
}Logs
DebugBundle.captureLog('Feature flag loaded', 'info', {
tags: { feature: 'new-checkout' },
});Context
DebugBundle.setContext('user', { id: '123', plan: 'team' });
DebugBundle.setContext('feature-flags', { newCheckout: true });Unload-Safe Flushing
The SDK ensures events are delivered even when the user navigates away or closes the tab:
visibilitychange→ hidden: Triggers immediate flush vianavigator.sendBeacon()beforeunload: Fallback flush viafetch()withkeepalive: true- Beacon payload limit: If the payload exceeds the browser's beacon limit (~64 KB), the SDK splits into multiple beacon calls
No events are lost during normal page navigation, tab close, or browser quit.
Volume Control
The Browser SDK enforces the same storm suppression as the Node SDK:
| Control | Threshold | Behavior |
|---|---|---|
| Duplicate dedup | 3 events in 30s | Identical events beyond the 3rd are suppressed |
| Loop detection | 10+ events in 2s | Enters suppression mode |
| Suppression checkpoint | Every 30s | One aggregate error_suppressed event |
| Auto-recovery | 60s silence | Resets to normal capture |
Combined with sessionSampleRate and maxEventsPerSession, you have three independent layers of volume control.
Trigger Tokens
Trigger tokens let support teams or internal tools force-enable capture for a specific user session, even if that session was excluded by sampling.
// Append ?_dbtoken=<trigger-token> to any page URL
// or programmatically:
DebugBundle.activateTriggerToken('trg_xxxx');When activated, the SDK overrides sessionSampleRate and enables full capture for the remainder of that session.
Safety Guarantees
The Browser SDK is designed to be invisible to your application:
- Never throws — All SDK code runs inside
try/catch. Failures are swallowed silently. - Never blocks rendering — Event capture and flush are asynchronous.
- Preserves application requests — Fetch wrapping must not drop caller headers or change authentication semantics.
- Minimal bundle size — Tree-shakeable, no heavy dependencies.
- CSP-friendly — No
eval(), no inline scripts, no dynamic code generation.
Next Steps
- Node.js SDK — Server-side capture with framework and logger integrations
- SDKs Overview — Universal interface and language support matrix