SaaS Pro Max
Sign inStart free
Documentation menu

Script tag

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

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

AttributeDefaultMeaning
data-key—Required. Your public key
data-hosthttps://saaspro.devIngest host
data-auto-pageviewson$pageview on load and route changes
data-auto-errorsonwindow errors and unhandled rejections
data-vitalsonLCP, CLS, INP, FCP, TTFB
data-outbound-linkson$outbound on cross-host link clicks
data-data-attributeson[data-spm-event] and [data-spm-form]
data-click-idsonAd click ids and _fbp / _fbc / _ttp cookies
data-privacy-modepersistentephemeral keeps tracking identity in memory; see privacy modes
data-cross-domain-originsunsetComma-separated exact origins for anonymous journeys; requires matching Data settings
data-cross-domain-auto-linkonDecorate ordinary same-tab links to configured journey origins
data-cookielessofflocalStorage only, no cookies
data-respect-dntoffDisable when the browser sends Do Not Track
data-consentgrantedpending buffers in memory until you grant
data-session-timeout-minutes30Idle window before a new session
data-batch-size20Events per automatic flush
data-flush-interval-ms2000Flush timer
data-release data-env data-app-version—Stamped on every event
data-debugoffLog every queued event

A bare attribute counts as true:

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

HTML
<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:

HTML
<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:

HTML
<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.

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

Text
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.