Product analytics
How people use your app, next to how it runs.
The browser SDK records pageviews and the events you send with track(), without cookies or local storage. Unhandled turns them into visitors, top pages, sources, sessions and funnels, in the same project as your traces and errors. Users who sign in appear under Users, with the requests they made and the trace of each.
- Overview
- Visitors, visits, bounce rate, conversion
- Funnels
- 2 to 8 steps, with drop-off per step
- No cookies
- Daily visitor IDs, no stored IP address
- Tracked users
- Requests, errors and traces per user
Funnels from pages and events
Build a funnel of 2 to 8 steps on the Funnels tab. A step matches a page URL, a route such as /p/:slug or a custom event, and the results show how many visits reached each step in order and where the rest stopped.
- Each step shows conversion from the first and the previous step, and the median time between them
- The dropped off link lists the visits that stopped right before a step
- Steps live in the URL, so you share a funnel by sharing its link
Pageviews and events from one integration
Add analytics() to the browser SDK. It records the first page and every path change, and track() sends a named event with up to 20 properties. From an AdonisJS or Node.js backend, track returns a result, so you can log a rejected event instead of losing it.
- Pages group by your route function, an unhandled-route meta tag, or the path with ids replaced by :id
- UTM values on the landing URL are attached to every pageview and event of that page load
- Keys such as email or user_id are rejected, so events never carry a person's identity
import { analytics, errors, initUnhandled, network } from '@unhandled/browser'
export const unhandled = initUnhandled({
dsn: import.meta.env.VITE_UNHANDLED_DSN,
environment: 'production',
integrations: [
network(),
errors(),
analytics({ ignorePaths: [/^\/admin/] }),
],
})
unhandled.track({
name: 'newsletter_signup',
properties: { source: 'footer' },
})Filter whole visits by page, source or event
Click a row in Pages, Sources, Campaigns or Events and the whole Overview filters on it. With an Event filter set, a Conversion card shows the share of visitors who sent that event, and an Event properties card lists the values they sent.
- Filters combine with AND and live in the URL, so a filtered view can be shared
- Each card compares with the equally long period just before the range
- A numeric property shows its sum, average, minimum and maximum
Every visit, step by step
The Sessions tab lists visits with their source, campaign, entry and exit page, pageviews, events and duration. Open one to see its journey, each pageview and event in order with its offset from the start of the visit.
- A Live badge marks visits with activity in the last 5 minutes
- Events show their property keys but never their values
- Each visit lists the services that sent its events
Visitors counted without cookies
Unhandled never stores an IP address or User-Agent with an analytics event. It keeps a hash of the site, address and User-Agent under a key that changes every UTC day, so the same person gets an unrelated ID the next day and old IDs cannot be recomputed.
- Crawlers, HTTP libraries and headless browsers are dropped before an ID is computed
- Country, browser, operating system, device and screen size are not recorded
- The SDK does not check Do Not Track, so leave analytics() out to honor it
import { analytics, errors, initUnhandled, network } from '@unhandled/browser'
const honorDoNotTrack = navigator.doNotTrack === '1'
export const unhandled = initUnhandled({
dsn: import.meta.env.VITE_UNHANDLED_DSN,
integrations: [
network(),
errors(),
...(honorDoNotTrack ? [] : [analytics()]),
],
})From a signed-in user to the trace that failed
Analytics stays anonymous, and Users comes from your traces instead. Call setUser once a request is authenticated, and Users lists who made the most requests and who hit server errors. A user's page shows their requests by status, the latency they saw and their latest 100 requests, each linked to its trace.
- Search a customer's name, email or ID to find their row
- The trace shows the failing span and any exception it recorded
- The same exception is grouped in Issues with every other user it affected
Send from
SDKs and tools
- Browser SDKanalytics() for pageviews, track() for custom events.
- AdonisJSUnhandled.track for server events, setUser for Users.
- Node.jstrack() from @unhandled/node in any Node.js app.
- OpenTelemetryOTLP log records with the unhandled.analytics scope.
- MCP serverOverview, sessions, funnels and users for your AI assistant.
- UsersIdentified users with their requests, errors and traces.
Questions
Try it on your own stack.
Every feature is on every plan, priced by the telemetry you send.