Documentation

Browsonic SDK Documentation

How to install and configure @browsonic/sdk, the browser SDK behind Browsonic. Applies to @browsonic/sdk 3.32.0. Framework adapters (React, Vue, Svelte, Angular, Next.js, Astro, Remix) are on the framework adapters page.

Last updated:

Introduction

Browsonic is browser error tracking, Session Replay and Browsonic Atlas for web apps. You add the SDK, @browsonic/sdk, to your web app; it sends what it captures to your Browsonic workspace, and you look at it in the console at app.browsonic.com. The SDK is on the public npm registry under the Apache-2.0 licence.

What it captures

Errors
Uncaught errors and unhandled promise rejections.
Console
console.error by default. Add levels with captureLevels.
Network failures
Failed fetch and XHR requests, and failed script, stylesheet and image loads.
Page views
Route changes, which build the Atlas map.

How it behaves

TopicBehaviour
SizeAbout 28 kB gzipped for @browsonic/sdk/core and 46 kB for @browsonic/sdk with the default collectors.
DependenciesOne: @rrweb/record, used only by the Session Replay entry. npm installs it with the package.
BatchingUp to 25 events per request, sent every 10 seconds and when the page is hidden. fatal events are sent at once. Not configurable.
DuplicatesThe same error is sent at most once per 60 seconds. The next occurrence carries the number of repeats that were skipped.
OfflineWhile the browser is offline, unsent events stay in memory (up to 200) and are sent when it is back online. When the page closes, what does not fit in the browser's final request is stored in localStorage and sent by the next page load, if that is within 24 hours.
RedactionE-mail addresses, tokens, card numbers, IBANs, phone numbers and Turkish ID numbers are replaced before anything is sent. See Privacy & Security.
Fail-safeErrors inside the SDK never reach your app. After repeated internal failures the SDK pauses itself.

Installation

Install the SDK from npm. Current version: 3.32.0.

npm

Terminal window
npm install @browsonic/sdk

pnpm

Terminal window
pnpm add @browsonic/sdk

yarn

Terminal window
yarn add @browsonic/sdk

The package contains ES modules, CommonJS and TypeScript types, and has five entry points. The sizes are the SDK's gzip size budgets.

Import fromContainsSize limit
@browsonic/sdkThe SDK with the default collectors. Use this one.46.9 kB
@browsonic/sdk/coreThe SDK without automatic capture: only what you send with captureError() and captureMessage(), plus the plugins below. trackPageViews and trackWebVitals have no effect here.28.2 kB
@browsonic/sdk/replaysessionReplayPlugin(), including rrweb.60 kB
@browsonic/sdk/widgetwidgetPlugin() for widget rules.8 kB
@browsonic/sdk/atlasatlasPlugin(), the Atlas task API, for /core setups. The main entry adds it when you set atlas: true.5 kB

Script tag (self-hosted)

The published code targets ES2020. If your build cannot parse ES2020 (for example webpack 4 with terser 4), use the self-hosted script that ships in the package. Browsonic publishes no hosted script snippet: you serve this file from your own site.

  1. Copy node_modules/@browsonic/sdk/dist/browser/browsonic.iife.es2015.js into your static files.
  2. Load it after a config block:
<script>
window.Browsonic = {
config: {
apiEndpoint: 'https://api.browsonic.com',
appKey: 'YOUR_APP_KEY',
apiKey: 'pk_live_YOUR_KEY',
},
};
</script>
<script src="/assets/browsonic.iife.es2015.js"></script>

The script starts the SDK from window.Browsonic.config and logs a console warning that names the endpoint. It contains the @browsonic/sdk entry, so Session Replay and the widget are not available. Add trackWebVitals: true to the config to collect Web Vitals.

Quick Start

You need two values from the console:

  1. App key. In Settings → Apps, create an app (for example checkout-web). Use its key as appKey.
  2. API key. Your workspace has a publishable key (pk_live_…) under Settings → API Keys; use it as apiKey. The console shows a key's full value only once, when it is created or regenerated. The first-run setup reveals it for you; later, a workspace owner can press Regenerate, which also stops the old value from working. A publishable key can only send data, so it is safe in browser code. Never put a secret sk_… key in browser code; the SDK warns if it sees one.

Basic setup

import { getBrowsonic } from '@browsonic/sdk';
const sdk = getBrowsonic();
sdk.init({
apiEndpoint: 'https://api.browsonic.com',
appKey: 'YOUR_APP_KEY',
apiKey: 'pk_live_YOUR_KEY',
environment: 'production',
release: 'YOUR_BUILD_ID', // the release you upload source maps under
});

Rules:

  • Get the SDK with getBrowsonic(). Do not use new Browsonic(): the framework adapters find the SDK through getBrowsonic() and do not see other instances.
  • Call init() once. While the SDK is starting or running, a second call returns false; call destroy() first.
  • init() returns false when apiEndpoint or appKey is missing or invalid, or apiKey is missing while page views are on (the console shows [Browsonic] Invalid config: and the reason). It also returns false inside a browser-extension page and for known bot user agents.
  • Start-up finishes when the browser is idle, within 2 seconds. await sdk.start() resolves true once the SDK is running. Errors raised before then are kept (up to 50) and sent after start-up.

Optional plugins

Web Vitals, Session Replay and widget rules are plugins. Register them before init():

import { getBrowsonic, webVitalsPlugin } from '@browsonic/sdk';
import { sessionReplayPlugin } from '@browsonic/sdk/replay';
import { widgetPlugin } from '@browsonic/sdk/widget';
const sdk = getBrowsonic();
// Register plugins before init(). A register() call after init() is ignored.
sdk.register(webVitalsPlugin()); // Web Vitals
sdk.register(sessionReplayPlugin()); // Session Replay (Professional, Enterprise)
sdk.register(widgetPlugin()); // Widget rules
sdk.init({
apiEndpoint: 'https://api.browsonic.com',
appKey: 'YOUR_APP_KEY',
apiKey: 'pk_live_YOUR_KEY',
});

Captured without extra code

  • Uncaught errors and unhandled promise rejections (level error).
  • console.error (error). For other console methods, see captureLevels.
  • Failed fetch and XMLHttpRequest calls: HTTP 4xx is warn; HTTP 5xx and requests that fail without a response are error; aborted requests are info.
  • Failed loads: <script> and <link> are error; images and media are warn.
  • Page views, which build the Atlas map.
  • A timeline of recent console calls, network requests, route changes and the clicks that changed the route, attached to each event.

Sessions

A session is one visit in one browser tab. It ends after 30 minutes without activity, and on newSession(), clearIdentity() or destroy(). A reload within 30 minutes continues the session.

For framework apps, see the framework adapter guides: the adapters report errors your framework catches itself (error boundaries, error handlers) and send route templates for Atlas.

Configuration

Options accepted by init() in @browsonic/sdk 3.32.0, grouped by purpose.

Required

OptionWhat it is
apiEndpointhttps://api.browsonic.com. Origin only; a path is ignored.
appKeyThe key of an app from Settings → Apps. A key that is not registered is refused with HTTP 403 INVALID_APP_KEY.
apiKeyYour publishable key (pk_live_…) from Settings → API Keys. Every request to the API is authenticated with it.

Environment & release

OptionDefaultWhat it does
environment'production'Label shown on every event.
releasenullBuild identifier. Source maps are matched by it, or by clientVersion when it is not set.
clientVersionnullYour app's version, shown under Versions in the console.
debugfalseLogs SDK activity to the console. Takes effect only when passed to init().

Capture & sampling

OptionDefaultWhat it does
captureLevels['error']Console levels sent as events. 'warn' adds console.warn. 'info' adds console.info, console.log and console.debug.
sampleRate0.1Share of sessions (0–1) that send info and warn events. See Sampling & quotas.
minSampleRate0Lowest rate the per-app sample rate from the console can set.
respectGPCtrueHonours Global Privacy Control. See Privacy & Security.
hasConsentednullConsent callback. See Privacy & Security.
visitorIdStrategy'session'Where the visitor ID is kept: 'session' (sessionStorage, one per tab), 'localStorage', 'cookie' (browsonic_vid, one year, this host only), or 'none' (a new ID for every request).

Page views & Atlas

OptionDefaultWhat it does
trackPageViewstrueSends page views, which build the Atlas map.
pageViewSampleRate1Share of sessions that send page views for route changes. The first page view of each page load is always sent.
atlasfalseLets a framework adapter take over page views with route templates, without double counting, and turns on the atlas task API. Page views, titles and navigation labels are sent without it.
normalizePageViewRoutestrueTurns URLs into route keys before sending (/users/123 → /users/:id) and removes the query string and fragment.
pageViewRoutePatterns[]Rewrites applied first, as { test: RegExp, replace: string } pairs.
beforeSendPageViewnull(route) => string | null. Return a new route, or null to drop the page view.
hashRoutingdetectedtrue when the URL fragment starts with #/ at init(). The fragment is then used as the route (#/users/123 → /users/:id).
manualPageViewsfalseOnly trackPageView() sends page views.
captureDocumentTitletrueSends document.title (redacted, up to 120 characters) with page views.
captureNavTriggerstrueSends the accessible name of the click or submit that caused a route change.

Timeline & network

OptionDefaultWhat it does
includeTelemetrytrueAttaches the timeline to events.
captureXHRtrueAlso records XMLHttpRequest. fetch is always recorded.
networkTelemetrytrueRecords requests in the timeline. With false, no request appears in the timeline; failed requests are still sent as network_error events.
trackNavigationtrueRecords route changes in the timeline.
trackVisitorfalseRecords every click and input in the timeline. Input values are never recorded. See Clicks and inputs.
visitor{ click: true, input: true }Which interactions trackVisitor records.
captureStorage{ local: false, session: false }Attaches localStorage and sessionStorage to events. keys is an allow-list. redactShapes: true also masks e-mail, phone, national ID, IBAN and card numbers inside the values.
captureCookieValuesfalseSends cookie values. Cookie names are always sent.
sessionContext{ allowKeys: [] }extra: an object, or a function that returns your own session fields. Values are redacted unless the key is in allowKeys; keys that match the redaction key lists stay masked. At most 32 keys and 8 KB.

Redaction

OptionDefaultWhat it does
redactKeystoken, password, authorization, secret, key, credential, authKey names whose values are masked in storage, cookies, user and context data. Setting it replaces this list, but the same seven words keep matching through redactKeyPatterns unless you set that too.
redactKeyPatternsthe same seven wordsMatched against the parts of a key name: token matches authToken and csrf_token, but key does not match keyboard.
redactPatterns[]Extra regular expressions, replaced with [REDACTED] in messages, URLs and referrers. Use the g flag.
redactCookieNames[]Cookies whose values are always masked (case-insensitive match on the name). Only matters with captureCookieValues: true.

Noise & guards

OptionDefaultWhat it does
ignoreMessages[]Drops errors whose message contains one of these strings (case-insensitive).
ignorePatterns[]Drops errors whose stack contains one of these strings. COMMON_THIRD_PARTY_PATTERNS is exported for common third-party scripts.
ignoreUrls[]Drops errors from pages whose URL contains one of these strings.
ignoreExtensionstrueDrops errors from browser-extension code.
ignoreScriptErrorstrueDrops cross-origin Script error. events, which carry no details.
abortForBotstrueThe SDK does not start for known bot user agents. botPatterns replaces the built-in list.
abortInExtensionContexttrueThe SDK does not start inside a browser-extension page.

Plugins, transport & diagnostics

OptionDefaultWhat it does
trackWebVitalsfalseSame as register(webVitalsPlugin()), for @browsonic/sdk and the self-hosted script. No effect with /core.
disableReplayfalseTurns Session Replay off, whatever the plan allows.
widgetRules, widgetRulesEndpoint, widgetPosition, cspNonce[], false, 'bottom-right', nullUsed only with widgetPlugin(). widgetRulesEndpoint: true loads the rules created under Widget rules in the console (Starter and above); rules you pass in widgetRules run in the browser on any plan.
persistQueuefalseKeeps unsent events in localStorage (IndexedDB when localStorage is full) across reloads.
compressautomaticGzips requests when the server accepts it. false: never. true: whenever the browser supports CompressionStream.
captureAsyncStackfalse'global' is deprecated. To keep the stack of where a callback was registered, wrap it with Browsonic.wrap(fn) (see the API reference).
internalDiagnosticsfalseSends the SDK's own metrics once a minute, for the console's SDK Health and Diagnostics pages.

Hooks

OptionDefaultWhat it does
onErrornull(event) => boolean | void. Runs for every captured event before it is queued. Return false to drop it; you may change the event.
onErrorStormnull(phase, count) => void. Called with 'enter' when 20 or more error events arrive within 10 seconds, and with 'exit' when the next error finds fewer than 10 in the last 10 seconds.
onIngestRejectednull({ status, reason, subscriptionStatus }) => void. Called when the API refuses a batch (HTTP 4xx other than 429), once per status per page load. Those events are dropped.
onUnsupportedVersionnull(minVersion, currentVersion) => void. Called when the API reports that this SDK version is too old.

Ignored since 3.25.0

These options are accepted, have no effect, and are named once in the browser console at init(): flushIntervalMs, requestTimeoutMs, maxBatchSize, cooldownMs, maxPayloadBytes, errorStormThreshold, errorStormWindowMs, errorStormCooldownMultiplier, internalDiagnosticsIntervalMs, maxStackFrames, maxQueueSize, maxValueLength, maxTelemetryEntries, visitor.inputThrottleMs, captureStorage.maxEntries.

Changing options after init()

updateConfig(partial) merges objects and replaces arrays. It ignores the following keys (logged only with debug: true): apiEndpoint, appKey, apiKey, captureXHR, networkTelemetry, trackNavigation, trackVisitor, trackPageViews, normalizePageViewRoutes, pageViewRoutePatterns, beforeSendPageView, hashRouting, pageViewSampleRate, manualPageViews, captureNavTriggers, captureDocumentTitle, trackWebVitals, atlas, captureAsyncStack, persistQueue, sessionContext. Changes to debug, captureLevels and internalDiagnostics have no effect either, and page views keep the environment, clientVersion, visitorIdStrategy, respectGPC, hasConsented and redactPatterns from init(). To change any of these, call destroy() and then init().

Example

import { getBrowsonic, COMMON_THIRD_PARTY_PATTERNS } from '@browsonic/sdk';
getBrowsonic().init({
// Required
apiEndpoint: 'https://api.browsonic.com',
appKey: 'YOUR_APP_KEY',
apiKey: 'pk_live_YOUR_KEY',
// Release
environment: 'production',
release: '2026.09.25-a1b2c3d',
clientVersion: '4.2.0',
// What to capture
captureLevels: ['error', 'warn'],
trackVisitor: true,
// Noise
ignorePatterns: COMMON_THIRD_PARTY_PATTERNS,
ignoreMessages: ['ResizeObserver loop'],
});

API Reference

Methods on the object returned by getBrowsonic().

MethodWhat it does
init(config) / start()Starts the SDK (see Quick Start). start() returns a Promise that resolves true once the SDK is running.
register(plugin)Adds a plugin. Call it before init().
captureError(error, options?)Reports an Error you caught. options: { handled?: boolean, mechanism?: string }.
captureMessage(message, level?)Reports a message. level is 'info' (default), 'warn', 'error' or 'fatal'. info and warn messages come only from sampled sessions.
setUser(user) / clearUser()Attaches your account's user fields to later events. See Identify users.
identify({ msisdn, userId }) / clearIdentity()Links one person across your apps. See Identify users.
setPersona(persona)Tags page views with a user type for Atlas. See Identify users.
setTag(key, value) / removeTag(key)An indexed key/value on later events.
addMetadata(key, value) / removeMetadata(key) / clearMetadata()The same store as tags.
setContext(name, object) / removeContext(name) / clearContexts()A named object, shown as its own panel on the event.
setExtra(key, value) / removeExtra(key) / clearExtras()A free-form value on later events.
withScope(fn)Tags, contexts, extras and user set inside fn apply only to events captured inside fn.
addBreadcrumb({ category, message?, level?, data? })Adds an entry to the timeline. Breadcrumbs cannot be removed.
trackPageView(route?, { name?, screen? })Sends a page view, optionally with a route template such as /users/:id.
newSession()Ends the session, starts a new one and returns its ID.
flush()Sends queued events now. Returns a Promise.
pause() / resume()Stops and restarts sending. Events, page views and recordings produced while paused are dropped; events queued before pause() are kept.
destroy()Removes listeners and timers, clears user, identity, tags and contexts, and ends the session. Unsent events are discarded unless persistQueue is on, so call flush() first.
getState()Returns 'uninitialized', 'initializing', 'running', 'paused' or 'destroyed'.
getPendingCount()Number of events waiting to be sent.
enterCriticalPath({ reason }) / exitCriticalPath() / isInCriticalPath()For critical steps such as checkout. Until exitCriticalPath(), or for at most 5 minutes, only error and fatal events are sent, the timeline and the widget pause, and events carry the reason. Options: captureOnly, suspendTelemetry, suspendWidget, autoExitMs.
getSessionHealth() / markSessionCrashed()Returns 'ok', 'errored' or 'crashed'; marks the session as crashed.
updateConfig(partial)Changes options after init(). See Configuration for the keys it ignores.

Browsonic.wrap(fn) is a static method on the class: import { Browsonic } from '@browsonic/sdk'. It wraps a callback so that errors thrown in it also carry the stack of where it was wrapped. It is not available in the self-hosted script.

captureError

try {
await riskyOperation();
} catch (error) {
sdk.captureError(error as Error, { handled: true });
}

captureMessage

sdk.captureMessage('Checkout completed'); // info
sdk.captureMessage('Payment provider timed out', 'error');

Event Types

TypeLevelSource
errorerrorAn uncaught error, or captureError().
unhandledrejectionerrorAn unhandled promise rejection.
network_errorwarn / error / infoA failed fetch or XHR: HTTP 4xx (warn); HTTP 5xx and requests without a response (error); aborted requests (info). A failed <script> or <link> load (error), or image or media load (warn).
console_errorerrorconsole.error(), or captureMessage(message, 'error').
console_warnwarnconsole.warn() with 'warn' in captureLevels, or captureMessage(message, 'warn').
console_infoinfoconsole.info() and console.log() with 'info' in captureLevels, or captureMessage(message).
console_debuginfoconsole.debug() with 'info' in captureLevels.
fatalfatalcaptureMessage(message, 'fatal'). Sent at once.
web_vitalinfoThe Web Vitals plugin.

Timeline

Every event carries a timeline of what happened before it. In the console it is the Timeline panel beside the stack trace.

Console
Console calls, redacted, at most 20 a second.
Network
fetch and XHR requests: method, sanitised URL, status and duration.
Navigation
Route changes, tab hidden and shown, offline and online.
Clicks
The control that changed the route. Every click and input with trackVisitor.

The SDK keeps the most recent entries in a fixed-size buffer; its size is not configurable. info and warn events carry a shorter timeline than errors. You can add your own entries with addBreadcrumb().

sdk.init({
// …required options
includeTelemetry: true, // attach the timeline to events
captureXHR: true, // record XMLHttpRequest as well as fetch
networkTelemetry: true, // requests in the timeline
trackNavigation: true, // route changes in the timeline
trackVisitor: false, // every click and input (see Clicks and inputs)
});

Clicks and inputs

By default the timeline records only the clicks that change the route. Set trackVisitor: true to record every click and input.

Input values are never recorded

Only the length of a value and its shape are sent. Password fields are skipped.

RecordedExample
Element tagbutton, input
Text of buttons and links (up to 200 characters, redacted)Submit order
Field labelE-mail address
Identifying attributes: id, class, name, type, role, aria-label, placeholder, href and a few moreid="submit-btn"
Value length16
Value shapeemail, numeric, alpha, alphanumeric

Add data-browsonic-mask to an element to keep its text and labels out of these records.

sdk.init({
// …required options
trackVisitor: true,
visitor: { click: true, input: true },
});

Identify users

setUser(user) describes the account in your own system. It takes any fields (id, email, plan, …), which are attached to later events. Keys that name credentials or phone and ID numbers are masked, and string values go through the same scrub as messages: a value of 32 or more letters, digits, _ or - (a UUID, for example) is sent as [REDACTED]. Call clearUser() on logout.

sdk.setUser({ id: 'user-123', email: 'user@example.com', plan: 'premium' });
// On logout
sdk.clearUser();

identify({ msisdn, userId }) links one person across the apps in your workspace. The User timeline in the console (Professional and Enterprise) shows that person's errors and warnings from all your apps, looked up by this number.

  • msisdn is the person's phone number, in any format. The SDK keeps the digits; a value that is not 10–15 digits long is dropped and clears any number set before.
  • userId is optional and also sets user.id.
  • The number is not sent when the browser sends Global Privacy Control.
  • Call clearIdentity() on logout. It also starts a new session.
  • Use identify() only where linking this data to the person's number is lawful for you.
sdk.identify({ msisdn: '+90 532 111 00 01', userId: 'acct-4821' });
// On logout
sdk.clearIdentity();

setPersona(persona) tags every page view with one user type, so Atlas can split the map by it. Examples: 'dealer', 'call-center', 'guest'.

  • The value is trimmed and lowercased. It must then be letters and digits joined by - or ., at most 32 characters; otherwise it is dropped and the previous persona stays.
  • It is kept in sessionStorage for the tab, so it survives reloads. setPersona(null) clears it.
  • Declare the values in the Atlas catalog. A value that is not declared is stored without a persona.
sdk.setPersona('dealer');
sdk.setPersona(null); // clears it

Session Replay

Session Replay records what the user saw and did (the page structure and interactions) and plays it back next to their errors. It needs a Professional or Enterprise plan; on other plans the plugin records nothing.

import { getBrowsonic } from '@browsonic/sdk';
import { sessionReplayPlugin } from '@browsonic/sdk/replay';
const sdk = getBrowsonic();
sdk.register(sessionReplayPlugin()); // before init()
sdk.init({
apiEndpoint: 'https://api.browsonic.com',
appKey: 'YOUR_APP_KEY',
apiKey: 'pk_live_YOUR_KEY',
});

When it records

A session is recorded only if all of these hold:

  • the plan includes Session Replay;
  • the session is selected: the rate is the lowest of the plugin's sampleRate, the rate set for the app in the console, and the plan's ceiling;
  • the browser does not send Global Privacy Control (with respectGPC: true);
  • hasConsented(), if you set it, returns true;
  • disableReplay is not true;
  • the workspace's replay storage is not full. When storage passes 90%, at most 25% of sessions are recorded; at 100%, none are.

An error captured during a recording links to that moment in the replay.

Plugin options

OptionDefaultWhat it does
sampleRate1Share of sessions to record (0–1). The console rate and the plan ceiling can only lower it. 0 turns recording off.
maskAllTexttrueReplaces all text before upload.
maskAllInputstrueMasks input values. Password and hidden inputs stay masked even with false.
blockAllMediatrueImages, SVG, video, audio, embeds and iframes (including payment and sign-in frames) become same-size placeholders. Canvas content is never recorded.
blockSelectornoneExtra CSS selector to block.
inlineFontstrueEmbeds the fonts the page shows (same origin, up to 200 KB each) so playback looks right. Adds about 50–200 KB to a session's first snapshot. false turns it off.
inlineImagesfalseEmbeds images that are same-origin or served with CORS, scaled to at most 1280 px. Needs blockAllMedia: false.
collectFontsfalseRecords fonts created with the FontFace API.
idleTimeoutMs120000Pauses after 2 minutes without user input (a hidden tab counts as idle) and resumes on the next click or key press. 0 disables.
maxDurationMs600000Stops recording 10 minutes after the session's first recording started, across reloads and idle pauses. A new session starts a new budget. 0 disables.
flushIntervalMs10000How often recorded segments are uploaded.
checkoutEveryNms300000Interval between full snapshots.

Settings and retention

In the console, a workspace owner opens Admin → Session Replay to set each app's recording rate (up to the plan's ceiling) and to see retention and storage use, and purge recordings. Recordings are kept for 90 days on Professional and 365 days on Enterprise, in a per-workspace store of 5 GiB and 10 GiB. See Data retention.

Atlas screen captures are rendered from Session Replay recordings, so they show what the recording shows, masked by default.

Web Vitals

import { getBrowsonic, webVitalsPlugin } from '@browsonic/sdk';
const sdk = getBrowsonic();
sdk.register(webVitalsPlugin()); // before init()
sdk.init({
apiEndpoint: 'https://api.browsonic.com',
appKey: 'YOUR_APP_KEY',
apiKey: 'pk_live_YOUR_KEY',
});

Or set trackWebVitals: true in init(). That is the only way with the self-hosted script.

MetricMeasuresAttribution sent
LCPWhen the largest element renderedElement, resource URL and route
INPInteraction latencyTarget element, event type, input delay, processing and presentation times, and route
CLSUnexpected layout shiftThe element that shifted, and route
FCPWhen the first content rendered—
TTFBServer response timeRedirect, DNS, connect and request times, and route
FIDFirst input delay, the predecessor of INP—

Values are sent when the page is hidden, as one info event, so they come only from sampled sessions (see Sampling & quotas); they also appear in the timeline of later errors. They do not count toward the monthly event limit. The console's Performance page shows LCP, INP and CLS at the 75th percentile per route.

SDK 3.21.0 and 3.21.1 send no Web Vitals. Use 3.21.2 or later.

Source maps

Source maps turn minified stack frames back into your original code. They need a Professional or Enterprise plan; on Free and Starter the upload is refused with HTTP 402.

  1. Token. A workspace owner creates an upload token in Admin → Source Maps in the console. It starts with sm_upload_ and works for one app. Treat it as a build-time secret: set it as BROWSONIC_SOURCEMAP_TOKEN in CI, never in browser code.
  2. Emit source maps that your bundles do not reference: Vite and Rollup sourcemap: 'hidden'; webpack devtool: 'hidden-source-map'; esbuild sourcemap: 'external'.
  3. Upload them at build time with a build plugin or the CLI.

Install the build plugins with npm install --save-dev @browsonic/build-tools. They need Node 20 or later.

Vite

vite.config.ts
import { defineConfig } from 'vite';
import { browsonicSourceMaps } from '@browsonic/build-tools/vite';
export default defineConfig({
build: { sourcemap: 'hidden' },
plugins: [browsonicSourceMaps({ appKey: 'YOUR_APP_KEY', release: process.env.RELEASE })],
});

Webpack

webpack.config.js
const { BrowsonicSourceMapsPlugin } = require('@browsonic/build-tools/webpack');
module.exports = {
devtool: 'hidden-source-map',
plugins: [new BrowsonicSourceMapsPlugin({ appKey: 'YOUR_APP_KEY', release: process.env.RELEASE })],
};

Rollup and esbuild: import { browsonicSourceMaps } from '@browsonic/build-tools/rollup' (or /esbuild), with the same options.

CLI

Terminal window
BROWSONIC_SOURCEMAP_TOKEN=sm_upload_… BROWSONIC_API_ENDPOINT=https://api.browsonic.com \
npx @browsonic/cli upload-sourcemaps --dist-path ./dist --release "$RELEASE" --app-key YOUR_APP_KEY

Next.js

withBrowsonicConfig(nextConfig, { sourceMaps: { appKey: 'YOUR_APP_KEY' } }) from @browsonic/nextjs adds the webpack plugin to production browser builds. Set productionBrowserSourceMaps: true in your Next.js config and install @browsonic/build-tools as a dev dependency. The plugin does not run under Turbopack, which Next.js 16 uses for next build unless you pass --webpack. Next.js serves the .map files it writes next to your JavaScript, so remove them from .next/static after the upload if they should not be public.

Plugin options

OptionDefaultWhat it does
appKeyrequiredYour app key.
releaseBROWSONIC_RELEASE, then the git short SHA (with -dirty for uncommitted changes), then the package.json versionThe release the maps are stored under. If none can be found, the upload is skipped.
tokenBROWSONIC_SOURCEMAP_TOKENUpload token. If it is missing, the plugin prints a warning and skips the upload; the build continues.
baseUrlBROWSONIC_API_ENDPOINT, then https://api.browsonic.comAPI address. The CLI has no default: set BROWSONIC_API_ENDPOINT or --base-url.
urlPrefixnoneThe path your assets are served under, if it is not the site root.
debugIdInjectionfalseStamps a debug ID into each bundle and its map. Frames are then matched by debug ID first.
bailOnErrorfalseFails the build when an upload fails.
dryRunfalseLists what would be uploaded without sending it. It still needs a token.

How maps are matched

The release you upload under must equal the SDK's release option, or its clientVersion when release is not set. With debugIdInjection: true, frames are matched by debug ID first and by release otherwise. Events store at most 64 characters of the release, so keep it to 64 characters or fewer.

Limits. Each map can be at most 10 MiB. release and appKey may use only A–Z a–z 0–9 . _ -.

Maps stay in your build output. The plugins do not delete .map files. Remove them from what you deploy if they should not be served.

In the console, open an event and press Symbolicate to resolve its frames: each frame then shows the original file, line and column with the surrounding code.

Sampling & quotas

What is sampled

  • Always sent: error and fatal events, from every session.
  • Sampled sessions only: info and warn events. That covers captureMessage at info or warn, console calls other than console.error (when captureLevels includes them), HTTP 4xx responses, aborted requests, failed image and media loads, and Web Vitals.
  • Page views: not affected by sampleRate. The first page view of every page load is always sent. Later route changes follow pageViewSampleRate (default 1), decided once per session. Page views you send with trackPageView() are always sent.
  • Session Replay: has its own rate (see Session Replay).

Which rate applies

  • The SDK decides once per session whether the session is sampled.
  • The rate starts as sampleRate (default 0.1). A workspace owner can set a sample rate for each app in Settings → Apps (0.001 to 1, default 0.1). Once the SDK has fetched it, it replaces sampleRate from the next page load, and it can be higher or lower. minSampleRate is the lowest rate it can set.
  • After HTTP 429 responses, or when the monthly limit is almost used, the SDK lowers its own rate for info and warn events, down to one eighth.

Duplicates

The same error is sent at most once per 60 seconds. During an error storm (20 or more errors in 10 seconds) the window is 5 minutes.

Plan limits

FreeStarterProfessionalEnterprise
Events per month5,000100,0001,000,000no limit
Requests per minute606005,00015,000
Apps1325999
API keys1110999
  • API keys. Free and Starter have one publishable key, which a workspace owner can regenerate in Settings → API Keys. Creating more keys, including secret keys for the REST API, needs Professional or Enterprise.
  • Requests per minute. For a publishable key, the limit applies per app and per client network (/24 for IPv4, /64 for IPv6); all secret keys of a workspace share one limit. Separately, each IP address can send at most 600 event requests a minute. Over a limit, the API answers HTTP 429 with Retry-After, and the SDK waits (up to about 5 minutes) and retries.
  • Monthly event limit. It resets on the 1st of each month (UTC). When it is used up, the API answers HTTP 429 and refuses new events until the next month. Page views, Web Vitals, Session Replay recordings and events dropped by noise filters do not count toward it.
  • Usage is shown in Settings → Usage.

Data retention

DataFreeStarterProfessionalEnterprise
Events, Web Vitals, sessions and page views7 days14 days90 days365 days
Session Replay recordings——90 days365 days
Session Replay storage——5 GiB10 GiB

The replay values your workspace uses are shown in Admin → Session Replay.

Privacy & Security

Sent by default

  • Error messages and stack traces, after redaction.
  • console.error messages as events; other console calls in the timeline, redacted.
  • Requests in the timeline: method, sanitised URL, status and duration. Failed requests are also sent as events, with status text, sizes and the allowed response headers.
  • Page URL, referrer and title, after redaction; route keys and the label of the control that caused a route change.
  • Browser and device: user agent, language, time zone, screen and viewport size, device pixel ratio, device memory, CPU cores, connection type, and the React, Vue, Angular or jQuery version the page uses.
  • A session ID, and a visitor ID kept in sessionStorage for one tab.

Never read: request and response bodies. Of the headers, only content-type, content-length, cache-control, etag, x-request-id, x-correlation-id, traceparent, tracestate, server and x-served-by are sent.

Off until you turn it on: click and input tracking (trackVisitor), storage snapshots (captureStorage), cookie values (captureCookieValues), Web Vitals, Session Replay and the Atlas task API (atlas).

Redaction in the browser

In messages, stacks, URLs, titles and the timeline, these are replaced with [REDACTED]:

  • e-mail addresses;
  • JWTs;
  • tokens of 32 or more letters, digits, _ or -;
  • runs of 12–19 digits, with or without spaces or dashes (card-shaped);
  • IBANs;
  • Turkish national ID numbers (checksum-validated);
  • phone numbers: with a + country code, in Turkish national format, or next to words such as phone, msisdn, tel or gsm;
  • key=value pairs whose key names a credential.

In URLs, the values of sensitive query parameters are also redacted, for example password, token, session, code, otp, email, key, iban, msisdn and tckn. Add your own patterns with redactPatterns.

By key name. In storage, cookies, user data, contexts and extras, values are masked when the key, or a part of it, is token, password, authorization, secret, key, credential or auth (so auth_token and apiKey are masked, keyboard is not), together with your redactKeys. setUser() also masks keys such as msisdn, phone, tel, gsm, tckn and iban.

Cookies. Cookie names are always sent. Values are sent only with captureCookieValues: true. Session and CSRF cookies are always masked, for example JSESSIONID, PHPSESSID, connect.sid, csrftoken, laravel_session, and names starting with __Host- or __Secure-.

On the server. The API redacts e-mail addresses, JWTs, secrets, card numbers, phone numbers, Turkish ID numbers and IBANs again before storing messages, stacks, URLs, the timeline, contexts and user data.

Page hints

  • data-browsonic-mask: keeps an element's text out of navigation labels and click records. It does not affect page titles or Session Replay.
  • data-browsonic-label="…": the exact label to send for a control (up to 80 characters).
  • data-browsonic-screen="…": names the current screen.
  • data-browsonic-screen-id="…": gives the current screen a stable ID.

Global Privacy Control

With respectGPC: true (the default), a browser that sends Global Privacy Control sends no errors, messages, network events, Web Vitals, page views or recordings. With atlas: true, Atlas task events are still sent, with a new random visitor ID on each request. Set respectGPC: false to ignore the signal.

Consent

hasConsented is a function you provide:

  • It returns true: data is sent.
  • It returns false: everything captured so far is dropped, recording stops, and nothing more is sent. With atlas: true, Atlas task events are the exception, sent with a random visitor ID.
  • Anything else, or an error (for example while your consent tool loads): events are held until an answer arrives. Page views are held for up to 10 seconds.

Stopping everything

pause() stops all sending until resume(). Events, page views, Atlas events and recordings produced while paused are dropped.

Legal terms: Data Processing Agreement and Privacy Policy. To report a vulnerability, see the security page.

Troubleshooting

Nothing arrives

  1. Open the browser console. [Browsonic] Invalid config: means init() returned false; the message names the problem.
  2. await sdk.start() should resolve true. sdk.getState() is 'initializing' for up to 2 seconds after init().
  3. Turn on logging: pass debug: true to init() and reload. updateConfig({ debug: true }) has no effect.
  4. Your browser may send Global Privacy Control. With the default respectGPC: true, nothing is sent from it.
  5. The SDK does not start inside browser-extension pages or for bot user agents (abortInExtensionContext, abortForBots).
  6. Framework adapters report nothing if the SDK was created with new Browsonic() instead of getBrowsonic(), or if init() never ran in the browser.

Requests fail

Check the Network tab for requests to api.browsonic.com.

StatusMeaningFix
401Missing, revoked or expired API key.In Settings → API Keys, a workspace owner regenerates the publishable key; put the new value in apiKey.
402The subscription is inactive, or the feature is not in your plan (for example source maps on Free or Starter).Check Settings → Billing.
403 INVALID_APP_KEYappKey is not an app of this API key's workspace.Create the app in Settings → Apps, or fix the key.
403 ORIGIN_NOT_ALLOWEDThe app enforces an allowed-origins list and your site's origin is not on it.Add the origin to the app in Settings → Apps.
413Request too large. The API accepts up to 1 MiB and SDK batches are far smaller, so a proxy in between is refusing it. The SDK resends the first half of the batch and drops the rest.Raise the proxy's request size limit.
429Rate limit or monthly event limit reached.Check Settings → Usage.

The SDK prints a console warning for 401, 402 and 403 responses. To handle a rejection in code, use onIngestRejected.

Some events are missing

  • info and warn events come only from sampled sessions (see Sampling & quotas).
  • Only console.error is captured by default (captureLevels).
  • ignoreMessages, ignorePatterns, ignoreUrls and an onError that returns false drop events.
  • Cross-origin Script error. events are dropped. To get their details, load third-party scripts with crossorigin="anonymous" and serve them with CORS headers.

Too many events

  1. Keep captureLevels: ['error'].
  2. Lower the app's sample rate in Settings → Apps. This reduces info and warn events and Web Vitals; errors are always sent.
  3. Drop known noise with ignoreMessages and ignorePatterns, for example COMMON_THIRD_PARTY_PATTERNS.

Batching and duplicate suppression are fixed and cannot be tuned.

Stack traces are still minified

  • Check that your plan is Professional or Enterprise.
  • Check that the upload's release equals the SDK's release (or clientVersion), and is at most 64 characters.
  • If your assets are not served from the site root, set urlPrefix.