Documentation menu

Plain HTML

No framework, no build step, no npm. One script tag.

Minimal page

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Acme</title>

    <script
      defer
      src="https://saaspro.dev/spm.js"
      data-key="spm_pub_prod_xxxxxxxx"
    ></script>
  </head>
  <body>
    <h1>Acme</h1>
  </body>
</html>

That is the whole integration: sessions, pageviews, outbound links, errors and web vitals all start on load. See script-tag.md for every data- attribute.

Tracking clicks without JavaScript

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

<a
  href="/docs"
  data-spm-event="nav clicked"
  data-spm-prop-target="docs"
>Docs</a>

data-spm-prop-plan-name="x" becomes { planName: "x" } and "true" / "false" become booleans. The nearest tagged ancestor wins, so you can tag a whole card:

<article data-spm-event="card clicked" data-spm-prop-id="pricing">
  <h2>Pro</h2>
  <p>Everything you need.</p>
  <button>Choose</button>
</article>

Forms

<form
  action="/subscribe"
  method="post"
  data-spm-form="newsletter"
  data-spm-prop-placement="footer"
>
  <label for="email">Email</label>
  <input id="email" name="email" type="email" required />
  <button type="submit">Subscribe</button>
</form>

Submitting sends $form_submit with { form: "newsletter", formId: "", action: "/subscribe", placement: "footer" }. Field values are never captured.

Tracking from your own scripts

Because the tag is deferred, queue calls rather than assuming window.spm exists:

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

Once the page has loaded, call window.spm directly:

<script>
  document.getElementById("buy").addEventListener("click", function () {
    window.spm.track("purchase started", { plan: "pro" }, { value: 49, currency: "EUR" });
  });
</script>

Identifying a signed-in visitor

Render the identity from your server:

<script>
  window.spmq = window.spmq || [];
  window.spmq.push([
    "identify",
    "{{ user.id }}",
    { email: "{{ user.email }}", plan: "{{ user.plan }}" },
  ]);
</script>

and on sign-out:

<script>window.spm && window.spm.reset();</script>
<script
  defer
  src="https://saaspro.dev/spm.js"
  data-key="spm_pub_prod_xxxxxxxx"
  data-consent="pending"
></script>

<div id="banner" role="dialog" aria-label="Cookies">
  <p>We use first-party analytics.</p>
  <button id="accept">Accept</button>
  <button id="reject">Reject</button>
</div>

<script>
  document.getElementById("accept").onclick = function () {
    window.spm.consent("granted");
    document.getElementById("banner").hidden = true;
  };
  document.getElementById("reject").onclick = function () {
    window.spm.consent("revoked");
    document.getElementById("banner").hidden = true;
  };
</script>

Until consent is granted nothing is sent and nothing is written to storage. If you would rather never set a cookie at all, add data-cookieless — identifiers then live only in localStorage.

Multi-page sites

Nothing extra to do: each page load starts with the stored anonymous id and continues the same session for 30 minutes of activity. Put the tag in a shared header partial so it appears on every page.

Content Security Policy

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

connect-src covers both fetch and navigator.sendBeacon.

Serving the file from your own origin

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

Copy node_modules/@saaspro/browser/dist/spm.js into your assets directory and keep data-host pointing at your ingest host.

Checklist

  • The tag is in <head> with defer
  • The key starts with spm_pub_
  • Inline scripts use window.spmq.push([...]), not bare spm.…
  • CSP allows connect-src https://saaspro.dev
  • A visitor appears in App → Analytics → Live when you load the page