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
console.error by default. Add levels with captureLevels.fetch and XHR requests, and failed script, stylesheet and image loads.How it behaves
| Topic | Behaviour |
|---|---|
| Size | About 28 kB gzipped for @browsonic/sdk/core and 46 kB for @browsonic/sdk with the default collectors. |
| Dependencies | One: @rrweb/record, used only by the Session Replay entry. npm installs it with the package. |
| Batching | Up to 25 events per request, sent every 10 seconds and when the page is hidden. fatal events are sent at once. Not configurable. |
| Duplicates | The same error is sent at most once per 60 seconds. The next occurrence carries the number of repeats that were skipped. |
| Offline | While 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. |
| Redaction | E-mail addresses, tokens, card numbers, IBANs, phone numbers and Turkish ID numbers are replaced before anything is sent. See Privacy & Security. |
| Fail-safe | Errors 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
npm install @browsonic/sdkpnpm
pnpm add @browsonic/sdkyarn
yarn add @browsonic/sdkThe package contains ES modules, CommonJS and TypeScript types, and has five entry points. The sizes are the SDK's gzip size budgets.
| Import from | Contains | Size limit |
|---|---|---|
@browsonic/sdk | The SDK with the default collectors. Use this one. | 46.9 kB |
@browsonic/sdk/core | The 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/replay | sessionReplayPlugin(), including rrweb. | 60 kB |
@browsonic/sdk/widget | widgetPlugin() for widget rules. | 8 kB |
@browsonic/sdk/atlas | atlasPlugin(), 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.
- Copy
node_modules/@browsonic/sdk/dist/browser/browsonic.iife.es2015.jsinto your static files. - 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:
- App key. In Settings → Apps, create an app (for example
checkout-web). Use its key asappKey. - API key. Your workspace has a publishable key (
pk_live_…) under Settings → API Keys; use it asapiKey. 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 secretsk_…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 usenew Browsonic(): the framework adapters find the SDK throughgetBrowsonic()and do not see other instances. - Call
init()once. While the SDK is starting or running, a second call returnsfalse; calldestroy()first. init()returnsfalsewhenapiEndpointorappKeyis missing or invalid, orapiKeyis missing while page views are on (the console shows[Browsonic] Invalid config:and the reason). It also returnsfalseinside a browser-extension page and for known bot user agents.- Start-up finishes when the browser is idle, within 2 seconds.
await sdk.start()resolvestrueonce 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 Vitalssdk.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, seecaptureLevels.- Failed
fetchandXMLHttpRequestcalls: HTTP 4xx iswarn; HTTP 5xx and requests that fail without a response areerror; aborted requests areinfo. - Failed loads:
<script>and<link>areerror; images and media arewarn. - 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
| Option | What it is |
|---|---|
apiEndpoint | https://api.browsonic.com. Origin only; a path is ignored. |
appKey | The key of an app from Settings → Apps. A key that is not registered is refused with HTTP 403 INVALID_APP_KEY. |
apiKey | Your publishable key (pk_live_…) from Settings → API Keys. Every request to the API is authenticated with it. |
Environment & release
| Option | Default | What it does |
|---|---|---|
environment | 'production' | Label shown on every event. |
release | null | Build identifier. Source maps are matched by it, or by clientVersion when it is not set. |
clientVersion | null | Your app's version, shown under Versions in the console. |
debug | false | Logs SDK activity to the console. Takes effect only when passed to init(). |
Capture & sampling
| Option | Default | What it does |
|---|---|---|
captureLevels | ['error'] | Console levels sent as events. 'warn' adds console.warn. 'info' adds console.info, console.log and console.debug. |
sampleRate | 0.1 | Share of sessions (0–1) that send info and warn events. See Sampling & quotas. |
minSampleRate | 0 | Lowest rate the per-app sample rate from the console can set. |
respectGPC | true | Honours Global Privacy Control. See Privacy & Security. |
hasConsented | null | Consent 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
| Option | Default | What it does |
|---|---|---|
trackPageViews | true | Sends page views, which build the Atlas map. |
pageViewSampleRate | 1 | Share of sessions that send page views for route changes. The first page view of each page load is always sent. |
atlas | false | Lets 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. |
normalizePageViewRoutes | true | Turns 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. |
beforeSendPageView | null | (route) => string | null. Return a new route, or null to drop the page view. |
hashRouting | detected | true when the URL fragment starts with #/ at init(). The fragment is then used as the route (#/users/123 → /users/:id). |
manualPageViews | false | Only trackPageView() sends page views. |
captureDocumentTitle | true | Sends document.title (redacted, up to 120 characters) with page views. |
captureNavTriggers | true | Sends the accessible name of the click or submit that caused a route change. |
Timeline & network
| Option | Default | What it does |
|---|---|---|
includeTelemetry | true | Attaches the timeline to events. |
captureXHR | true | Also records XMLHttpRequest. fetch is always recorded. |
networkTelemetry | true | Records requests in the timeline. With false, no request appears in the timeline; failed requests are still sent as network_error events. |
trackNavigation | true | Records route changes in the timeline. |
trackVisitor | false | Records 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. |
captureCookieValues | false | Sends 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
| Option | Default | What it does |
|---|---|---|
redactKeys | token, password, authorization, secret, key, credential, auth | Key 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. |
redactKeyPatterns | the same seven words | Matched 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
| Option | Default | What 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. |
ignoreExtensions | true | Drops errors from browser-extension code. |
ignoreScriptErrors | true | Drops cross-origin Script error. events, which carry no details. |
abortForBots | true | The SDK does not start for known bot user agents. botPatterns replaces the built-in list. |
abortInExtensionContext | true | The SDK does not start inside a browser-extension page. |
Plugins, transport & diagnostics
| Option | Default | What it does |
|---|---|---|
trackWebVitals | false | Same as register(webVitalsPlugin()), for @browsonic/sdk and the self-hosted script. No effect with /core. |
disableReplay | false | Turns Session Replay off, whatever the plan allows. |
widgetRules, widgetRulesEndpoint, widgetPosition, cspNonce | [], false, 'bottom-right', null | Used 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. |
persistQueue | false | Keeps unsent events in localStorage (IndexedDB when localStorage is full) across reloads. |
compress | automatic | Gzips requests when the server accepts it. false: never. true: whenever the browser supports CompressionStream. |
captureAsyncStack | false | 'global' is deprecated. To keep the stack of where a callback was registered, wrap it with Browsonic.wrap(fn) (see the API reference). |
internalDiagnostics | false | Sends the SDK's own metrics once a minute, for the console's SDK Health and Diagnostics pages. |
Hooks
| Option | Default | What it does |
|---|---|---|
onError | null | (event) => boolean | void. Runs for every captured event before it is queued. Return false to drop it; you may change the event. |
onErrorStorm | null | (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. |
onIngestRejected | null | ({ 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. |
onUnsupportedVersion | null | (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().
| Method | What 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'); // infosdk.captureMessage('Payment provider timed out', 'error');Event Types
| Type | Level | Source |
|---|---|---|
error | error | An uncaught error, or captureError(). |
unhandledrejection | error | An unhandled promise rejection. |
network_error | warn / error / info | A 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_error | error | console.error(), or captureMessage(message, 'error'). |
console_warn | warn | console.warn() with 'warn' in captureLevels, or captureMessage(message, 'warn'). |
console_info | info | console.info() and console.log() with 'info' in captureLevels, or captureMessage(message). |
console_debug | info | console.debug() with 'info' in captureLevels. |
fatal | fatal | captureMessage(message, 'fatal'). Sent at once. |
web_vital | info | The 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.
fetch and XHR requests: method, sanitised URL, status and duration.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.
Only the length of a value and its shape are sent. Password fields are skipped.
| Recorded | Example |
|---|---|
| Element tag | button, input |
| Text of buttons and links (up to 200 characters, redacted) | Submit order |
| Field label | E-mail address |
Identifying attributes: id, class, name, type, role, aria-label, placeholder, href and a few more | id="submit-btn" |
| Value length | 16 |
| Value shape | email, 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 logoutsdk.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.
msisdnis 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.userIdis optional and also setsuser.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 logoutsdk.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 itSession 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, returnstrue;disableReplayis nottrue;- 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
| Option | Default | What it does |
|---|---|---|
sampleRate | 1 | Share of sessions to record (0–1). The console rate and the plan ceiling can only lower it. 0 turns recording off. |
maskAllText | true | Replaces all text before upload. |
maskAllInputs | true | Masks input values. Password and hidden inputs stay masked even with false. |
blockAllMedia | true | Images, SVG, video, audio, embeds and iframes (including payment and sign-in frames) become same-size placeholders. Canvas content is never recorded. |
blockSelector | none | Extra CSS selector to block. |
inlineFonts | true | Embeds 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. |
inlineImages | false | Embeds images that are same-origin or served with CORS, scaled to at most 1280 px. Needs blockAllMedia: false. |
collectFonts | false | Records fonts created with the FontFace API. |
idleTimeoutMs | 120000 | Pauses after 2 minutes without user input (a hidden tab counts as idle) and resumes on the next click or key press. 0 disables. |
maxDurationMs | 600000 | Stops recording 10 minutes after the session's first recording started, across reloads and idle pauses. A new session starts a new budget. 0 disables. |
flushIntervalMs | 10000 | How often recorded segments are uploaded. |
checkoutEveryNms | 300000 | Interval 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.
| Metric | Measures | Attribution sent |
|---|---|---|
LCP | When the largest element rendered | Element, resource URL and route |
INP | Interaction latency | Target element, event type, input delay, processing and presentation times, and route |
CLS | Unexpected layout shift | The element that shifted, and route |
FCP | When the first content rendered | — |
TTFB | Server response time | Redirect, DNS, connect and request times, and route |
FID | First 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.
- 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 asBROWSONIC_SOURCEMAP_TOKENin CI, never in browser code. - Emit source maps that your bundles do not reference: Vite and Rollup
sourcemap: 'hidden'; webpackdevtool: 'hidden-source-map'; esbuildsourcemap: 'external'. - 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
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
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
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_KEYNext.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
| Option | Default | What it does |
|---|---|---|
appKey | required | Your app key. |
release | BROWSONIC_RELEASE, then the git short SHA (with -dirty for uncommitted changes), then the package.json version | The release the maps are stored under. If none can be found, the upload is skipped. |
token | BROWSONIC_SOURCEMAP_TOKEN | Upload token. If it is missing, the plugin prints a warning and skips the upload; the build continues. |
baseUrl | BROWSONIC_API_ENDPOINT, then https://api.browsonic.com | API address. The CLI has no default: set BROWSONIC_API_ENDPOINT or --base-url. |
urlPrefix | none | The path your assets are served under, if it is not the site root. |
debugIdInjection | false | Stamps a debug ID into each bundle and its map. Frames are then matched by debug ID first. |
bailOnError | false | Fails the build when an upload fails. |
dryRun | false | Lists 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:
errorandfatalevents, from every session. - Sampled sessions only:
infoandwarnevents. That coverscaptureMessageat info or warn, console calls other thanconsole.error(whencaptureLevelsincludes 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 followpageViewSampleRate(default1), decided once per session. Page views you send withtrackPageView()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(default0.1). A workspace owner can set a sample rate for each app in Settings → Apps (0.001 to 1, default0.1). Once the SDK has fetched it, it replacessampleRatefrom the next page load, and it can be higher or lower.minSampleRateis 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
infoandwarnevents, 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
| Free | Starter | Professional | Enterprise | |
|---|---|---|---|---|
| Events per month | 5,000 | 100,000 | 1,000,000 | no limit |
| Requests per minute | 60 | 600 | 5,000 | 15,000 |
| Apps | 1 | 3 | 25 | 999 |
| API keys | 1 | 1 | 10 | 999 |
- 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
| Data | Free | Starter | Professional | Enterprise |
|---|---|---|---|---|
| Events, Web Vitals, sessions and page views | 7 days | 14 days | 90 days | 365 days |
| Session Replay recordings | — | — | 90 days | 365 days |
| Session Replay storage | — | — | 5 GiB | 10 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.errormessages 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=valuepairs 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. Withatlas: 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
- Open the browser console.
[Browsonic] Invalid config:meansinit()returnedfalse; the message names the problem. await sdk.start()should resolvetrue.sdk.getState()is'initializing'for up to 2 seconds afterinit().- Turn on logging: pass
debug: truetoinit()and reload.updateConfig({ debug: true })has no effect. - Your browser may send Global Privacy Control. With the default
respectGPC: true, nothing is sent from it. - The SDK does not start inside browser-extension pages or for bot user agents (
abortInExtensionContext,abortForBots). - Framework adapters report nothing if the SDK was created with
new Browsonic()instead ofgetBrowsonic(), or ifinit()never ran in the browser.
Requests fail
Check the Network tab for requests to api.browsonic.com.
| Status | Meaning | Fix |
|---|---|---|
| 401 | Missing, revoked or expired API key. | In Settings → API Keys, a workspace owner regenerates the publishable key; put the new value in apiKey. |
| 402 | The 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_KEY | appKey is not an app of this API key's workspace. | Create the app in Settings → Apps, or fix the key. |
403 ORIGIN_NOT_ALLOWED | The app enforces an allowed-origins list and your site's origin is not on it. | Add the origin to the app in Settings → Apps. |
| 413 | Request 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. |
| 429 | Rate 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
infoandwarnevents come only from sampled sessions (see Sampling & quotas).- Only
console.erroris captured by default (captureLevels). ignoreMessages,ignorePatterns,ignoreUrlsand anonErrorthat returnsfalsedrop events.- Cross-origin
Script error.events are dropped. To get their details, load third-party scripts withcrossorigin="anonymous"and serve them with CORS headers.
Too many events
- Keep
captureLevels: ['error']. - Lower the app's sample rate in Settings → Apps. This reduces
infoandwarnevents and Web Vitals; errors are always sent. - Drop known noise with
ignoreMessagesandignorePatterns, for exampleCOMMON_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
releaseequals the SDK'srelease(orclientVersion), and is at most 64 characters. - If your assets are not served from the site root, set
urlPrefix.