Browser monitoring is a beta AppSignal Labs feature. The
@appsignal/browser package is published under the beta tag on npm, and its
configuration and API may still change between beta releases. Share feedback
in our Discord community.GET /api/boom/ request: HTTP 500. You know the request failed, but not why. Distributed tracing follows that request from the browser into the back end that handled it and raised a RuntimeError. In this example, that is a Django app. Any back end that supports distributed tracing works the same way.
The service map shows both sides of that trace:

The service map for this trace: the back-end error connected to the browser error that started it.
- Browser: Add your API URL patterns to
tracePropagationTargets. Matching requests carry atraceparentheader. A 5xx response or qualifying timeout becomes a browser error. - Receiving back end: Instrument the service that handles those requests. The instrumentation reads
traceparentand reports spans through an AppSignal hosted collector. Use a supported integration or OpenTelemetry instrumentation for its framework.
Distributed tracing is a beta AppSignal Labs feature. The browser SDK needs no collector. The receiving service needs an AppSignal hosted collector for its spans.
Before you start
Your front end needs only the Browser SDK. The rest of the setup is in the back end: the service that handles your front end’s requests. In the example above, that is the Django service. It can be an API on its own host, or the same app that serves your pages. Set it up first, so its spans appear in the trace when a request fails:- Report through a hosted collector. On the Hosted Collectors page in your organization settings, open a collector and copy its Collector URL. Send the service’s data to that URL. If the list is empty, create a collector there first. Distributed tracing only works through a hosted collector. In Python and Ruby, set the URL as the
collector_endpointoption. Ruby also needs theappsignal-opentelemetrygem. - Set a service name. Give the service its own name. The trace uses it to tell the service apart from the browser and other services. Where you set it depends on the back end’s language:
- Instrument its HTTP server. Use an integration for its framework that reads
traceparentfrom incoming requests. That is how the service joins the browser’s trace instead of starting its own. In Go, also register a propagator withotel.SetTextMapPropagator. - Allow
traceparentacross origins. A back end can be on another origin, such asapi.example.comcalled fromwww.example.com. The browser may then send a CORS preflight before the request. Allowtraceparentin the back end’s response to the preflight. Otherwise the browser may block the request:
Enable it
Add a pattern for each back-end host and path totracePropagationTargets. The default empty list leaves tracing off:
fetch and XMLHttpRequest call. Because the SDK hooks into those browser APIs, setup is the same in React, Vue, or plain JavaScript. That includes libraries built on them, such as axios.
Patterns ignore the scheme and query string. A relative URL resolves against the page’s address, so a call to /api/orders from www.example.com matches www.example.com/api/**. Use * within one path segment or ** across segments. Include both a host and a path:
In the trace timeline,
serviceName labels the browser error span. For example, Checkout UI identifies errors from your checkout front end. The default is Browser. Use a different name for each front end you monitor. The SDK sends the name with every error report.
The Browser SDK install flow offers Distributed tracing as an option. It is off by default. Selecting it adds tracePropagationTargets: ["api.example.com/**"] to the browser snippet. Replace the example host with your back end’s host, then instrument the receiving service. The wizard does not install or configure that service.
To verify propagation, load a page that calls your API and inspect the request in your browser’s Network panel. A matching request should carry a traceparent header. Then inspect a failed request in AppSignal to check that back-end spans appear in its trace.
The SDK adds no header to a request made in no-cors mode, because the browser would strip it. Those requests are never reported as failed requests.
What is reported
The SDK reports these outcomes for a matching request that sent atraceparent header:
- A 5xx response is reported as an
HTTPError. - A timeout is reported as a
TimeoutError. That is afetchthat rejects with aTimeoutError, as one bounded byAbortSignal.timeout()does. It is also anXMLHttpRequestwhosetimeoutfires. The server may have received and traced a timed-out request.
POST https://api.example.com/orders: HTTP 503 or GET https://api.example.com/orders: timed out. Besides everything an ordinary browser error carries, the report includes:
- The request, as
params.request: theurl, themethod, thestatuswhen a response arrived, and theduration_ms. Query parameters are removed from the URL as set by your allowlist. - The span: the trace ID and span ID from the request’s
traceparent, with its start time and duration. AppSignal builds the browser’s span from these, so it covers the request as the browser saw it. After a timeout, the back end’s spans can run longer than the browser’s span. - The service name you set, or
Browser.
- A successful request. The header is sent, but the browser stays silent, so the back-end trace stands on its own.
- A 4xx response. The SDK does not classify it as a failed request for tracing.
- A request the browser could not deliver: refused, failed DNS, or blocked by an extension. No server handled it, so there is no back-end trace to join.
- A timeout while the device is offline, which says nothing about the back end.
- A request your code canceled.
privacy.networkBlocklist or breadcrumb collection is disabled. A breadcrumb for a request that propagated context includes its trace ID. Its Open trace button opens that trace’s timeline.
A failed request goes through the same pipeline as every other error. errors.enabled, errors.sampleRate, the rate limits, and the deduplication of repeats all apply. beforeError receives it as an HTTPError or TimeoutError, with the request under context.request. You can drop or redact it there. A request matching privacy.networkBlocklist is never reported.
The SDK avoids reporting the same fetch rejection twice when it also reaches an unhandled rejection handler or captureError(). If your code throws a new error after a 500 response, that error has its own trace. The failed request remains a separate report.
Reading it in AppSignal
Failed requests appear in the Errors view of the Browser section asHTTPError and TimeoutError issues. They are grouped by the route the user was on, like every other front-end error. Read more in front-end errors in AppSignal.
Open an issue to see its message, breadcrumbs, and tags in the side panel. Its Sample tab also shows the trace: the Trace service map, Trace timeline, and related log lines. Select Fullscreen in the Trace timeline card to open the view below. If the preview omits spans, the button reads All spans. Open full trace page, the icon next to the issue number, opens the full issue page.
In the fullscreen timeline, Browser labels the browser span’s service. Select that span to see the request and error details.
In the product page example, GET /api/boom/ returned HTTP 500. In the trace timeline, you can follow the failed request from the browser into the Django service:
Browsershows anHTTPErroron/product/chocolate-stroopwafel(85 ms). The selected span’s details show the request URL and HTTP 500 response.- Beneath it,
djangoshowsGET api/boom/with aRuntimeError(81 ms). - The Django request contains two
SELECTspans and aPOSTspan, with their durations.

The failed browser request and related Django spans in one trace.
Joining a trace your code started
Your application may already set a validtraceparent on a request, for example through an OpenTelemetry browser SDK. AppSignal for Browser then leaves that header alone, and a failed request joins the trace your code started. This holds for fetch and XMLHttpRequest alike.
On the issue’s Traces tab, open the trace timeline in fullscreen to inspect the back-end work.