Documentation menu

Script tag

One line, no build step. The build enforces a 12 KiB gzip budget.

<script
  defer
  src="https://saaspro.dev/spm.js"
  data-key="spm_pub_prod_xxxxxxxx"
></script>

Put it in <head>. On load it starts a session, sends a $pageview, follows SPA route changes, captures errors and web vitals, and exposes window.spm.

Configuration attributes

Attribute Default Meaning
data-key Required. Your public key
data-host https://saaspro.dev Ingest host
data-auto-pageviews on $pageview on load and route changes
data-auto-errors on window errors and unhandled rejections
data-vitals on LCP, CLS, INP, FCP, TTFB
data-outbound-links on $outbound on cross-host link clicks
data-data-attributes on [data-spm-event] and [data-spm-form]
data-click-ids on Ad click ids and _fbp / _fbc / _ttp cookies
data-privacy-mode persistent ephemeral keeps tracking identity in memory; see privacy modes
data-cross-domain-origins unset Comma-separated exact origins for anonymous journeys; requires matching Data settings
data-cross-domain-auto-link on Decorate ordinary same-tab links to configured journey origins
data-cookieless off localStorage only, no cookies
data-respect-dnt off Disable when the browser sends Do Not Track
data-consent granted pending buffers in memory until you grant
data-session-timeout-minutes 30 Idle window before a new session
data-batch-size 20 Events per automatic flush
data-flush-interval-ms 2000 Flush timer
data-release data-env data-app-version Stamped on every event
data-debug off Log every queued event

A bare attribute counts as true:

<script defer src="https://saaspro.dev/spm.js"
        data-key="spm_pub_prod_xxxxxxxx"
        data-cookieless
        data-auto-errors="false"
        data-env="production"
        data-release="site@2026-09-03"></script>

"", "true" and "1" mean true; anything else means false.

Tracking from your page

<script>
  spm.track("newsletter signup", { source: "footer" });
  spm.identify("user_123", { email: "ada@example.com", plan: "pro" });
</script>

Because the tag is deferred, window.spm may not exist yet when your own inline script runs. Queue calls instead:

<script>
  window.spmq = window.spmq || [];
  window.spmq.push(["identify", "user_123", { plan: "pro" }]);
  window.spmq.push(["track", "hero viewed", { variant: "b" }]);
</script>

Each entry is [methodName, ...arguments]; the queue is replayed in order as soon as the tag initialises, and spmq.push keeps working afterwards.

Declarative tracking

No JavaScript needed:

<button
  data-spm-event="cta clicked"
  data-spm-prop-position="hero"
  data-spm-prop-plan="pro"
>
  Start free
</button>

<form data-spm-form="signup" action="/subscribe" data-spm-prop-variant="b">
  <input name="email" type="email" required />
  <button type="submit">Subscribe</button>
</form>

data-spm-prop-plan-name="x" becomes { planName: "x" }. "true" and "false" are parsed as booleans. The nearest tagged ancestor wins, so wrapping a whole card in data-spm-event works.

<script defer src="https://saaspro.dev/spm.js"
        data-key="spm_pub_prod_xxxxxxxx"
        data-consent="pending"></script>

<script>
  function accept() { window.spm.consent("granted"); }
  function reject() { window.spm.consent("revoked"); }
</script>

While consent is pending nothing is sent and nothing is written to storage. Granting flushes what was buffered. Persistent mode stores identifiers; ephemeral mode keeps them in memory.

Content Security Policy

script-src 'self' https://saaspro.dev;
connect-src 'self' https://saaspro.dev;

connect-src covers both fetch and navigator.sendBeacon. If you serve the file from your own origin, point both at it and set data-host to your ingest host.

Verifying

Open App → Analytics → Live in the console and load your page. A visitor should appear within a couple of seconds. If not, add data-debug and watch the browser console — every queued event and every request is logged.