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

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
#setup

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
src/unhandled.ts
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' },
})
#filters

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
#sessions

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
#privacy

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
src/unhandled.ts
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()]),
  ],
})
#users

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
#faq

Questions

Try it on your own stack.

Every feature is on every plan, priced by the telemetry you send.