Tracking

Most signal is captured automatically. This page covers the JavaScript API for the events only you can name - signups, purchases, feature usage - plus every automatic event, the script options, and the privacy controls.

The JavaScript API

The snippet exposes window.analytics on every page:

Custom events
analytics.track("signup_completed", {
  plan: "professional",
  source: "pricing",
});

// properties: string | number | boolean | null
// limits: name ≤ 100 chars · ≤ 30 properties · strings ≤ 500 chars
Identity (optional - no email required)
// after your app authenticates the user:
analytics.identify("internal-user-id");

// visitor-level properties for later segmentation:
analytics.set({ tier: "beta" });
Manual pageview + privacy
analytics.page();     // manual pageview (rarely needed - SPAs are automatic)
analytics.optOut();   // stop tracking this browser + clear its ids
analytics.optIn();    // resume
Call track() at the moment the thing actually happens - on the server-verified success callback, not on the button click. "signup_completed" should mean the signup completed.

Calling the API before the script has loaded

The script loads deferred, so code that runs on page load (a framework mount effect, an inline script on a thank-you page) can fire before window.analytics exists. Define this stub once, before the tracker tag, and every call made early is queued and replayed in order the moment the tracker is ready. Nothing is dropped and nothing double counts.

Optional stub (put it above the tracker tag)
<script>
  window.analytics = window.analytics || { q: [] };
  ["track", "identify", "set", "page", "optOut", "optIn"].forEach(function (m) {
    window.analytics[m] = window.analytics[m] || function () {
      window.analytics.q.push([m].concat([].slice.call(arguments)));
    };
  });
</script>
<script defer src="https://focusstat.com/tracker.js" data-site="YOUR_SITE_ID"></script>

Without the stub, guard early calls with window.analytics?.track(...) and accept that a call made before load is lost. A queued page() is ignored: the tracker records the initial pageview itself.

Automatic events

These are collected without any code (each toggleable in Settings → Tracking). Names starting with $ are reserved:

EventWhat it means
$pageviewPage load or SPA route change. A change to the query string alone (filters, sorting, pagination) is not a pageview; hash changes are ignored unless enabled
$scrollScroll depth thresholds 25/50/75/90/100 - once per pageview
$clickClicks on interactive elements, with a stable element descriptor and heatmap coordinates
$rage_click3+ clicks within ~1s in a ~50px radius - user frustration
$dead_clickA click with no observable consequence (no navigation, DOM change, network, focus) - heuristic, with a confidence level
$quickbackVisited a page and returned within 10s - content/UX quality signal
$outbound / $downloadExternal link clicks and file downloads
$form_start / $form_submitFirst input into a form / form submission (never the field contents)
$error / $resource_errorJavaScript exceptions (fingerprinted into issues) and failed scripts/styles/images
$web_vitalReal-user LCP, INP, CLS, FCP, TTFB with ratings
$copy / $media_play / $media_pause / $search / $404Text copied (length only), media interactions, internal search terms, 404 pages

For stable click identity across redesigns, tag important elements yourself - this always wins over generated descriptions:

<button data-analytics-id="hero-signup">Start free</button>

On a wrapper, the id becomes a prefix instead: a button inside <section data-analytics-id="pricing"> is reported as #pricing button.buy, so each control keeps its own row. Clicks anywhere in a tagged wrapper are counted, but only real controls (links, buttons, form fields) can be flagged as dead clicks. Links that open a new tab, leave the site or point at the current page are never flagged.

Goals & conversions

A goal turns an event or a page visit into a conversion. Create them in Settings → Goals & Alerts: choose Visit a page and pick the page (exactly that page, or any page that starts with it, e.g. everything under /thanks), or choose Do something and pick a custom event such as signup_completed, or a built-in one: a form submit, an outbound link click or a file download. Sessions containing a goal are marked converted everywhere - Overview, sources, funnels, session explorer, the "Why?" engine.

Script options

AttributeEffect
data-hash-routing="true"Treat #hash changes as pageviews (hash routers)
data-cookieless="true"No persistent visitor id - returning visitors are not linkable across visits
data-respect-dnt="true"Never track browsers with Do Not Track / Global Privacy Control
data-autocapture="false"Disable click/form/media autocapture
data-scroll="false"Disable scroll tracking
data-errors="false"Disable error capture
data-strip-params="coupon,ref"Extra query params to strip from URLs before sending

Server-side settings (replay, network telemetry, exclusions) live in Settings → Tracking and Settings → Privacy - the tracker fetches them at load, so changes apply without touching your site.

Privacy controls

Always on: anonymous random visitor ids scoped to one site, no fingerprinting, raw IPs discarded after coarse geolocation, sensitive URL parameters (token, email, code, session…) stripped both client- and server-side, and URL fragments never stored.

Session replay masks passwords and all input values before anything leaves the browser. Mark your own sensitive regions:

<div data-analytics-mask>   masked text (shown as ***)   </div>
<div data-analytics-block>  never serialized at all      </div>
<div data-analytics-ignore> input events ignored          </div>

Excluding your own visits

Testing your own site constantly? Two ways to keep yourself out of the numbers (Settings → Privacy):

1 · Browser flag (recommended) - open your site once with ?sp_ignore=true appended, e.g. https://yoursite.com/?sp_ignore=true. That browser is permanently excluded for that site - it survives IP changes, works at home, on café wifi, anywhere. ?sp_ignore=false re-includes it. Do it once per browser and device you test with.

2 · IP exclusion - add your home/office IPs (exact, or prefixes like 85.34.12.*) in Settings → Privacy; the page shows your current IP with one click to copy it in. IPs are compared at ingestion and immediately discarded - they are never stored. Excluded traffic shows up as a counter on the Installation page so you can confirm it's working.

Data deletion (whole site, everything, or a single visitor) lives in Settings → General; retention windows for raw events and replays in Settings → Privacy and → Tracking. Next: learn to read the dashboard.