Skip to content

JavaScript API

Once s.js is on the page, the SDK exposes one global: window.statable.t(). Use it to fire custom events from anywhere, click handlers, framework lifecycle hooks, fire-and-forget instrumentation deep in your code.

This page covers manual events. For HTML attributes see Tracking script reference. For payload shape see Event payload reference.


Signature

window.statable.t(name: string, props?: Record<string, any>): void
ParameterTypeRequiredDescription
namestringyesHuman-readable event name. Title Case recommended.
propsobjectnoCustom properties merged into the event payload as p.*

Returns undefined. Fire-and-forget. No callback, no promise.


Basic usage

Event without props

window.statable.t('Sign Up');

Event with custom props

window.statable.t('Purchase', {
  plan: 'pro',
  amount: 49,
  currency: 'USD'
});

Property values can be strings, numbers, or booleans. Nested objects are accepted but flat types index better in the dashboard.


Framework examples

React

import { useEffect } from 'react';

function PricingPage() {
  useEffect(() => {
    window.statable.t('Pricing Viewed');
  }, []);

  const handleUpgrade = (plan) => {
    window.statable.t('Upgrade Clicked', { plan });
  };

  return <button onClick={() => handleUpgrade('pro')}>Upgrade</button>;
}

Vue

<script>
export default {
  mounted() {
    window.statable.t('Modal Opened', { modal: 'newsletter' });
  },
  methods: {
    submit() {
      window.statable.t('Newsletter Subscribed');
    }
  }
}
</script>

Plain DOM

<button id="cta">Start trial</button>
<script>
  document.getElementById('cta').addEventListener('click', () => {
    window.statable.t('Trial Started', { source: 'hero' });
  });
</script>

Safe-call pattern (script not yet loaded)

If your code might run before s.js finishes loading (inline scripts in <head>, or a deferred bundle that hasn't parsed yet), guard against the global being undefined. The simplest pattern is an existence check on the call site:

window.statable?.t?.('Early Event', { source: 'hero' });

The optional chaining drops the call when the global isn't there yet, so you don't crash with a reference error.

There is a second, narrower window. A classic <script> tag in <head> runs before <body> exists, so the SDK waits for DOMContentLoaded to read its configuration. In that gap window.statable.t already exists but the SDK is not configured, and a call made then is dropped without an error. Either way the deferred startup still sends its own pageview.

Neither case is queued and replayed. If you depend on an early event being recorded, fire it after DOMContentLoaded.


Auto-tracked events

Some events fire automatically. They appear in your dashboard alongside custom events.

TriggerEvent name (filterable)
Page load + SPA navigationpageview
Tab unload / hidden / 28-min inactivityengagement (system)
Click on <a> to a different hostOutbound Link Click
Click on <a> with download extension or download attrFile Download
Click on any element with data-statable-event="..."Your event name
Submit on <form data-statable-event="...">Your event name

Which of these actually fire depends on the modules enabled for the site under Site settings → Tracking Code. Pageviews are always on; the rest can be switched off.

engagement is a system event that powers session duration and scroll-depth metrics. Not meant to be filtered in the UI like a custom event. Treat it as infrastructure. Its inactivity timer fires at 28 minutes, deliberately inside the 30-minute window that closes a session server-side, so the final engagement lands before the session ends.

File Download matches: pdf, zip, rar, gz, tar, 7z, doc(x), xls(x), ppt(x), csv, exe, dmg, iso, mp3, mp4, avi, mov. Links with a download attribute count regardless of extension, as do links whose type attribute is anything other than text/html.


Naming conventions

  • Use Title Case: Sign Up, Pricing Viewed, Plan Changed.
  • Keep names short and human-readable. They appear in dashboards and reports.
  • Use property names, not event names, for variable data: 'Plan Changed', { from: 'free', to: 'pro' } instead of 'Plan Changed Free To Pro'.
  • Stay consistent across your codebase. Mismatched casing creates duplicate rows.

Reserved names

The SDK internally wraps custom event names in square brackets ([Event Name]) to distinguish them from system events on the backend. Do not pass names that already contain brackets. The names pageview and engagement are reserved and bypass the bracket wrapping.


Opt-out

Set a flag in localStorage to exclude a visitor from tracking:

localStorage.setItem('analytics_ignore', 'true');

The SDK checks this on every send and silently drops events when set. To re-enable:

localStorage.removeItem('analytics_ignore');

Scope: per-browser, per-domain (standard localStorage rules). Useful for excluding your own team, contractors, or honoring user-level Do-Not-Track preferences.


Bot detection

Two client-side checks skip tracking entirely:

  • navigator.webdriver === true. Set by Selenium, Cypress, Playwright, Puppeteer (when not stealth-patched).
  • window.Cypress. Additional Cypress-specific guard.

Server-side filtering (multiple UA parsers, IP heuristics) runs on top. See Event payload reference.


Status code for error pages

For 404, 410, or other non-200 pages, hint the status to the SDK with a meta tag in <head>:

<meta name="status:code" content="404">

Read on every pageview and added to the payload as sc. The dashboard uses it to separate broken-page traffic from healthy traffic and power the Error pages report.


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.