DebugBundle
SDKs

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-browser

Quick Setup

import DebugBundle from '@debugbundle/sdk-browser';

DebugBundle.init({
  transportMode: 'relay',
  endpoint: '/debugbundle/browser',
  environment: 'production',
  service: 'my-spa',
});

Calling init() automatically:

  • Hooks browser window error and unhandled-rejection events
  • Registers beforeunload / visibilitychange for 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

OptionTypeDefaultDescription
projectTokenstringRequired 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.
environmentstring"production"Environment name.
servicestringdocument.title or "browser-app"Service identifier.
endpointstringderived from transportRelay or ingestion endpoint. Set this to your relay path or backend relay URL when using relay transport.
transportMode'relay' | 'direct'inferredExplicitly select relay or direct ingestion. Use relay for absolute backend relay URLs in split frontend/backend deployments.
enabledbooleantrueKill switch.
sampleRatenumber (0–1)1.0Event-level sampling rate.
logLevelLogLevel"warning"Minimum log level to capture.
redactFieldsstring[]["password", "secret", "token", "authorization", "cookie", "ssn", "credit_card"]Field names to redact from captured data.
maxBreadcrumbsnumber10Maximum breadcrumbs retained in the ring buffer.
breadcrumbsOnErrorOnlybooleantrueWhen true, breadcrumbs attach only to error events instead of being shipped as standalone frontend_breadcrumb events.
captureNetworkbooleantrueCapture fetch and XMLHttpRequest calls as breadcrumbs; first-party promoted failures also emit request_event.
tracePropagationTargetsstring[]same-origin onlyCross-origin first-party URL substrings allowed to receive X-DebugBundle-Trace-Id.
captureClicksbooleantrueCapture click events with CSS selector paths.
captureRouteChangesbooleantrueCapture popstate and pushState/replaceState route changes.
captureConsolebooleanfalseOpt-in: capture console.error and console.warn.
networkFilterBrowserNetworkFilterConfigFilter captured network requests by URL pattern, header, or status code.
sessionSampleRatenumber (0–1)1.0Session-level sampling. Decided once at session start.
maxEventsPerSessionnumber100Hard cap per session. After this, only exceptions and first-party 5xx request events are captured.
batchSizenumber25Events per flush batch.
flushIntervalnumber (ms)3000Auto-flush interval.
maxBufferedEventsnumber500Max events in buffer before oldest are dropped.
requestTimeoutMsnumber (ms)5000HTTP transport timeout.
onDiagnostic(diagnostic) => voidCallback for SDK self-diagnostic events.
beforeSend(event) => event | nullSynchronous 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 crossorigin configured, so the browser hid the real stack.
  • Code dispatched a bare error event or threw a non-Error value.
  • 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 are a chronological trail of user actions and system events leading up to an error. They attach automatically to every captured exception.

Captured Types

TypeSourceExample
clickDOM click listenerbutton.submit-btn clicked
navigationpopstate / History APINavigated to /dashboard
networkfetch / XMLHttpRequestGET /api/users → 200 (142ms)
consoleconsole.error, console.warnconsole.error("timeout")
customDebugBundle.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 OptionTypeDescription
denyUrls(string | RegExp)[]URLs matching any pattern are excluded.
allowUrls(string | RegExp)[]If set, only matching URLs are captured.
captureRequestHeadersstring[]Allowlist of request headers to capture.
captureResponseHeadersstring[]Allowlist of response headers to capture.
captureRequestBodybooleanCapture request body (default: false).
captureResponseBodybooleanCapture 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:

FieldExample
userAgentMozilla/5.0 ... Chrome/125.0.6422.142 Safari/537.36
browserChrome 125.0.6422.142
osmacOS 15.1
deviceTypedesktop
screen2560x1440
viewport1280x720
devicePixelRatio2
touchCapablefalse
languageen-US
connectionType4g
colorSchemePreferencedark

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:

  1. visibilitychange → hidden: Triggers immediate flush via navigator.sendBeacon()
  2. beforeunload: Fallback flush via fetch() with keepalive: true
  3. 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:

ControlThresholdBehavior
Duplicate dedup3 events in 30sIdentical events beyond the 3rd are suppressed
Loop detection10+ events in 2sEnters suppression mode
Suppression checkpointEvery 30sOne aggregate error_suppressed event
Auto-recovery60s silenceResets 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

On this page