Browser errors and source maps

JavaScript errors in your front end are collected with the @errorbird/browser package. Stack traces from minified code are mapped back to the original code on the server, using the source maps you upload from your build pipeline:

RangeError: Invalid amount: -5                 RangeError: Invalid amount: -5
    at n (…/app.4f3c.js:1:34)          →          at formatPrice (src/checkout.ts:3:11)
    at …/app.4f3c.js:1:126                         at renderCart (src/checkout.ts:9:30)
    at e (…/app.4f3c.js:1:119)                     at renderCart (src/checkout.ts:9:16)

Grouping uses the mapped text too. The minifier gives functions different names in every build (n, e, q…) and shifts the columns; if the signature were calculated from those, every deploy would reopen every existing error as a new Issue.

Installation

npm install @errorbird/browser

At your application's entry point, before any other code:

import { init } from '@errorbird/browser'

init({
  key: 'ebrd_pub_…',                       // publishable key
  release: import.meta.env.VITE_RELEASE,   // e.g. "[email protected]" or a commit SHA
  environment: 'production',
})

That is enough to collect uncaught errors (window.onerror) and unhandled promise rejections (unhandledrejection). Reporting never breaks the page: network errors are swallowed, the SDK's own errors are not reported, and calls do nothing during server-side rendering (when there is no window).

The key. Use only the publishable key; requests must come from an origin on the key's allowed domain list. If the secret key ends up in your bundle, anyone can write on behalf of the project.

Sending errors manually

import { captureException, captureMessage, setTag } from '@errorbird/browser'

try {
  await checkout()
} catch (error) {
  captureException(error, { tags: { step: 'payment' } })
  throw error
}

setTag('tenant', 'acme')
captureMessage('The payment provider responded slowly', 'Warning')

In React, calling captureException(error) in an error boundary's componentDidCatch is enough.

Options

Option Default Description
key — Publishable key (required)
endpoint https://api.errorbird.com API root
release — Build version; source maps are matched by this name
environment project default production, staging…
tags — Tags added to every event (at most 30)
beforeSend — Changes the event before sending; returning null drops it
ignoreErrors — Errors whose message matches these strings/regexes are ignored
captureGlobalErrors true Whether to install the global listeners
maxEvents 50 Maximum number of events per page lifetime
sendQueryString false Whether to send the ?… part of the page URL

Noise protection: the same error is sent only once within 60 seconds. The uninformative Script error. (an error from a script loaded from another origin without a CORS header; the browser hides the details) and ResizeObserver loop warnings are skipped by default. The page URL is sent without its query string: parameters such as ?token=… often carry personal data or secrets.

Uploading source maps

Source maps are uploaded from the build pipeline (CI) with the secret key:

PUT /api/v1/sourcemaps?release=<release>&file=<minified file name>
X-API-KEY: ebrd_secret_…
Content-Type: application/json

<the map itself>

file is the file name as it appears in the stack trace (app.4f3c.js); the path and the query string are ignored, and a .map suffix is stripped. Uploading again for the same release and file replaces the old map (workers cache the parsed map, so the new one takes effect within 30 minutes at the latest; an upload under a new release name takes effect immediately). Maps are parsed and validated on upload; a broken map is rejected with 400 invalid_source_map. The limit is 20 MB per file.

An example CI step with Vite:

export RELEASE="web@$(git rev-parse --short HEAD)"
VITE_RELEASE="$RELEASE" npx vite build --sourcemap hidden

for map in dist/assets/*.js.map; do
  curl --fail -X PUT \
    "https://api.errorbird.com/api/v1/sourcemaps?release=$RELEASE&file=$(basename "$map" .map)" \
    -H "X-API-KEY: $ERRORBIRD_SECRET_KEY" \
    -H "Content-Type: application/json" \
    --data-binary "@$map"
done

# Do not ship the maps: they contain your original source code.
rm dist/assets/*.js.map

--sourcemap hidden produces the maps but does not add the //# sourceMappingURL comment to the bundle; browsers do not look for them and your source code is not published. The webpack equivalent is devtool: 'hidden-source-map', and in esbuild it is --sourcemap=external.

The release name must match exactly. If the release you give the SDK differs from the release used in the upload, nothing is mapped. The safest approach is to derive both from the same environment variable (RELEASE above).

How does it work?

  1. The SDK sends the error to POST /api/v1/ingest/browser. The endpoint uses the same quota, PII scrubbing and validation as the other ingest endpoints; the only difference is that it allows CORS.
  2. While processing the event, the worker looks at the release tag and the file names in the stack; if it finds a matching map, it maps every frame to the original file, line and column.
  3. A frame's function name is not taken from the minified name but found in the map's original source text (sourcesContent) as the function enclosing the position. For maps without source text, the map's names field is used; in that case the name is less stable across builds.
  4. The signature is calculated from the mapped stack. The minified version is kept in the event's metadata as minifiedStackTrace; if a map was uploaded for the wrong release, that is where you find the raw positions.

Both Chrome/Edge (V8: at fn (url:line:column)) and Firefox/Safari (fn@url:line:column) formats are supported. Lines that cannot be mapped (at Array.map (<anonymous>), third-party scripts without maps) stay as they are.

Timing. Upload the map before errors arrive; symbolication happens once, when the event is processed, and a map uploaded later does not change past events. A release with no map is remembered as "missing" for one minute, so the short race between a deploy and the upload affects at most a minute of events.

Management

In the dashboard, the Projects page summarizes each project's uploaded maps per release and lets you delete a release's maps. API equivalents:

Endpoint Permission
GET /api/v1/projects/{id}/sourcemaps Project member
DELETE /api/v1/projects/{id}/sourcemaps?release=<release> Admin

Deleting old releases' maps is a good habit; if errors still arrive from that release, they will no longer be mapped once its map is deleted.