Skip to content

Tracking script reference

The Statable tracking script is a small JavaScript bundle you embed once on every page you want to measure. It auto-tracks pageviews, engagement, scroll depth, outbound clicks, file downloads, and exposes window.statable.t() for custom events.

This page is the canonical reference for the bundle, HTML attributes, and install patterns. For the JavaScript API see JavaScript API. For wire format see Event payload reference.


The bundle

s.js, ~2.4 KB over the wire. Auto-tracks pageviews, engagement time, scroll depth, outbound link clicks, file downloads, form submits, and exposes window.statable.t() for custom events.

What a given s.js actually does depends on the modules enabled for that site under Site settings → Tracking Code. The list above is the full set; the Nano preset ships pageviews and SPA navigation only.

Embeddable widgets ship as their own bundles: luw.js (Live Users), mw.js (Visitor Map), gw.js (3D Globe), tcw.js (Top Countries). gp.js is a frozen legacy globe, still served so old embeds keep working. Documented under Embeddable widgets, not here.


Installation

Place the snippet in <head> with defer. defer ensures the script doesn't block HTML parsing and runs after the document is parsed.

<script defer src="https://statable.com/js/YOUR_SITE_ID/s.js"></script>

Replace YOUR_SITE_ID with the numeric Site ID from Site settings → Tracking Code. The SDK reads the ID from the URL path, so no data-id attribute is required on paid plans.


URL structure

PathUsed for
/js/{site_id}/s.jsStandalone tracker (paid plans)
/js/{hash}/{widget}.jsStandalone widget (paid plans) — gw, luw, mw, tcw
/js/{hash}/t/{widget}.jsWidget + tracker bundle (Hobby plan; tracker is bundled inside the widget)

The Hobby plan has no separate tracker — pick a widget and the install snippet is generated from /api/site/script-url.


Attributes reference

AttributeRequiredDefaultDescription
srcyes(none)URL of the bundle. Site ID is encoded in the path.
deferrecommended(none)Defer script execution until HTML is parsed
data-idnoderived from srcNumeric Site ID. Only needed for Hobby widget bundles (/t/...) as a fallback.
data-tracking-apino<script-origin>/api/eventOverride the ingest endpoint (proxy / self-hosted)
data-before-sendno(none)Name of a global function that mutates props before send
data-statable-{key}no(none)Sticky custom property attached to every event from this page load

data-tracking-api (optional)

By default the SDK posts events to <script-origin>/api/event. If your script is loaded from https://statable.com/js/YOUR_SITE_ID/s.js, the endpoint is https://statable.com/api/event.

Override this if you proxy traffic through your own domain (recommended for first-party tracking, ad-blocker resilience, or self-hosted ingesters):

<script defer
        src="https://statable.com/js/YOUR_SITE_ID/s.js"
        data-tracking-api="https://example.com/proxy/event"></script>

The endpoint must accept POST with Content-Type: text/plain and a JSON body. Exact shape: Event payload reference.


data-before-send (optional)

Enrich, redact, or sample events before they leave the browser. The value is the name of a global function (not an inline expression). The function receives the current props object and must return the modified object.

<script>
  function statableEnrich(props) {
    // Attach the current user ID, plan, and feature flags
    if (window.currentUser) {
      props.userId = window.currentUser.id;
      props.plan = window.currentUser.plan;
    }
    return props;
  }
</script>

<script defer
        src="https://statable.com/js/YOUR_SITE_ID/s.js"
        data-before-send="statableEnrich"></script>

Fires once per pageview, called with merged custom props (data-statable-* attributes + any per-call props). If the function throws, the SDK swallows the error silently and continues with unmodified props.


data-statable-* (optional)

Any attribute prefixed with data-statable- becomes a sticky custom property attached to every event sent from this page load. Useful for cohort, environment, or experiment tags.

<script defer
        src="https://statable.com/js/YOUR_SITE_ID/s.js"
        data-statable-cohort="beta"
        data-statable-env="production"
        data-statable-app-version="2024.4.28"></script>

Keys are forwarded verbatim (lowercased after the prefix), so the example produces:

{ "cohort": "beta", "env": "production", "app-version": "2024.4.28" }

These props are merged before data-before-send runs, so your hook can override or remove them.


SPA support

The SDK wraps history.pushState, history.replaceState, and listens for popstate / pageshow (back-forward cache). No configuration required. Pageviews fire automatically on client-side navigation in React Router, Vue Router, Next.js, SvelteKit, Astro, and any router that uses the History API.

If your router doesn't use history.pushState (rare), call window.statable.t('pageview') manually after each transition.


Performance impact

  • Bundle: ~2.4 KB over the wire (s.js — 5140 B uncompressed; served zstd 2457 B, brotli 2181 B, gzip 2539 B).
  • Loaded with defer: zero render-blocking, runs after DOMContentLoaded.
  • Network calls use fetch with keepalive: true so unload events don't delay navigation.
  • The inactivity timeout runs in a Web Worker, so a backgrounded tab still closes its session on time when the browser throttles ordinary timers. Engagement time itself is arithmetic on the main thread.
  • Scroll tracking uses requestAnimationFrame. Fires at most once per frame.

See also


Ready to take control of your web analytics? Try Statable free for 30 days. No credit card required, full feature access, built for GDPR. Start your free trial or view a live demo.