Docs

Script tag

The script-tag build works on any site that lets you edit the <head>: static HTML, CMSs, page builders, server-rendered apps.

<script>window.x3=window.x3||function(){(window.x3q=window.x3q||[]).push(arguments)}</script>
<script async src="https://api.3x.example/v1/s/x3.full.js?k=pk_3x_YOUR_PUBLISHABLE_KEY" data-key="pk_3x_YOUR_PUBLISHABLE_KEY"></script>

With the first-party proxy the same snippet points at your own domain – the proxy path maps 1:1 onto the API root:

<script>window.x3=window.x3||function(){(window.x3q=window.x3q||[]).push(arguments)}</script>
<script async src="/n3/v1/s/x3.full.js?k=pk_3x_YOUR_PUBLISHABLE_KEY" data-key="pk_3x_YOUR_PUBLISHABLE_KEY" data-host="/n3"></script>

Builds

FileContentsSize budget (gzip)
x3.jsCore: pageviews (incl. SPA navigation), sessions, attribution, scroll, engagement, clicks (CTA, WhatsApp, messengers, downloads, outbound), custom events, data-x3-event, consent state, queue, transport, plugin loader≤ 14.5 KB
x3.full.jsCore + every plugin below≤ 19.5 KB
x3.video.js, x3.sections.js, x3.forms.jsPlugins, loaded on demand when the project config enables them or data-plugins lists them≤ 2.5 KB each
x3.identify.jsidentify() normalization and hashing (calls before it loads are queued)≤ 2.5 KB
x3.consent.jsCMP adapters (Cookiebot, Usercentrics, Complianz, Borlabs, Google Consent Mode)≤ 2.5 KB
x3.bookings.jsBooking adapters (HubSpot meetings, Calendly, thank-you pages, window events)≤ 2.5 KB

The core is larger than a pageview-only counter because it also carries consent modes, the conversion beacon, the npm/presets bridge and client-side canonicalPage rules – features that must act before the first send. npm run size -w @3x-engine/sdk enforces these budgets in CI.

Versioned copies (x3@1.2.3.js) are served for pinning; unversioned files are cached for an hour (5 minutes with ?k=) and revalidated in the background.

?k=pk_… inlines the project's client configuration (window.__x3cfg), so the tracker starts without fetching GET /v1/c/{pk}. Without it the SDK fetches the config and holds sends until it arrives (at most 5 s, then defaults are used).

Attributes

AttributeDefaultMeaning
data-key– (required)Publishable key pk_3x_…
data-hostOrigin of the script's srcWhere batches and config go: the API origin or your proxy path (/n3)
data-pluginsfrom the project configComma list: video,sections,forms
data-consentread from the consent key / CMPpending or granted
data-autoallComma list of autocapture features to keep: pageviews,clicks,scroll,engagement,forms,video,sections
data-hash-routingoffTreat #/route changes as pageviews
data-debugoffExpose window.__x3 and log diagnostics

A later x3("init", {...}) call overrides attributes (SDK reference).

The queue

The first line defines window.x3 as a queue until the script has loaded. Everything you call before is replayed in order:

<script>
  x3("track", "newsletter_signup", { list: "weekly" });
  x3("consent", "granted");
</script>

Queue commands: init, track (alias event), goal, funnelStep, page, identify, consent, flush. After loading, window.x3 is the full API object as well as a function, so x3.track("…") works too.

Single-page apps

The core patches history.pushState/replaceState and listens to popstate, so client-side routing (Next.js, React Router, Vue Router, Astro view transitions, …) produces pageviews without extra code. Hash routers: data-hash-routing. Routers without the History API: call x3("page", "/path") yourself.

Loading the script twice (hot reload, duplicated tags) is harmless: a singleton guard keeps one tracker.

Content Security Policy

With the proxy: script-src 'self' and connect-src 'self' plus the hash of the inline stub. Without the proxy: add the API origin to both. Details: CSP.