Installing the Tracking Snippet

The Apex tracking snippet (apex.js) is a lightweight, self-contained script that runs on your website. It handles visitor identification, UTM attribution, experiment assignment, form interception, and event tracking — all automatically.

Info

One key, one snippet. Use the same workspaceKey for staging, preview, production, mobile builds, dev, TestFlight, App Store, Play closed tracks — every environment. Apex auto-detects the environment (no separate sandbox keys, no "live vs test" toggles to remember).

For mobile specifically, see TestFlight vs App Store for the per-channel detection rules.

Adding the Script Tag

Place this line in your site's <head>. Replace YOUR_WORKSPACE_KEY with the key from your Apex dashboard (Settings → Workspace → General, or the Set up Apex install step).

<script src="https://your-apex-domain/api/apex-js?key=YOUR_WORKSPACE_KEY" async></script>

The async attribute lets the browser download the script in parallel with HTML parsing, so it never holds up your page.

If you run visual experiments and visitors see the original version flash before the variant, you can add a one-line style that hides the page while the variant applies. It is optional and off by default. See Hiding the page while visual experiments apply.

Tip

You can also use the data-key attribute instead of a query parameter: <script src="https://your-apex-domain/api/apex-js" data-key="YOUR_WORKSPACE_KEY" async></script>

Installing on a Site Builder or via a Tag Manager

The script tag works anywhere you can add code: in the HTML head, through a site builder's custom code panel, or through a tag manager.

The optional hiding style is different. It only helps when it runs during HTML parse, before the body paints:

  • It can work on WordPress, Webflow, Squarespace (Business plan and up), Shopify, Framer, Ghost, and any platform with raw HTML access.
  • Leave it out on Wix, Plasmic, Google Tag Manager, Segment, and similar platforms whose "Custom Code → Head" feature actually renders into a body component or runs through a runtime injector. There the style lands after the page has painted and hides what was already on screen.

Tip

How to tell which camp your platform is in. After installing on a test page, view the page source (right-click → View Page Source, not the DevTools Elements panel — Elements shows the live DOM, source shows the raw HTML the server sent). If the code you pasted sits between <head> and </head>, your platform injects it during parse. If it's anywhere else (typically inside a <div> in the body), it's injected later and the hiding style should stay out.

WordPress

You have three options. From most to least technical:

  1. Edit your theme's header.php (or use a child theme) and paste the script tag just before </head>.
  2. Use a head-injection plugin like Insert Headers and Footers or WPCode. These render their settings server-side into the <head>. Paste the script tag in the "Header" section.
  3. Use Site Kit / Tag Manager if you're already managing tags through GA. Install the <script> tag there. If you want the hiding style, paste it directly into your theme (option 1 or 2), never into the tag manager.

Webflow

Workspace Settings → Custom Code → Head Code. Paste the script tag. Webflow renders custom code server-side into the head, so the hiding style also works here if you need it.

Squarespace

Settings → Advanced → Code Injection → Header. Paste the script tag. Available on Business plan and above. Squarespace renders header code server-side.

Shopify

Edit theme.liquid (or your active theme's main layout) and paste the script tag just before </head>. Never paste the hiding style into Shopify's "Additional scripts" field for checkout — that field is loaded asynchronously and behaves like a tag manager.

Framer

Site Settings → General → Custom Code → End of <head>. Paste the script tag. Framer renders custom code into the static HTML head.

Plasmic

Plasmic's hosted sites are the awkward case here, similar to Wix. Plasmic Studio's "Custom Code → Head" panel sounds like head injection, but it actually renders the code into a body-level component on the published page.

For Plasmic hosted sites, install only the <script> tag:

<script src="https://your-apex-domain/api/apex-js?key=YOUR_WORKSPACE_KEY" async></script>

Visitors will see a brief content swap on pages with running visual experiments before the variant applies. It is a Plasmic architecture limitation that affects every experiment tool the same way. Don't add the hiding style in Plasmic; it would hide a page that is already showing.

For Plasmic code-export to Next.js (or another framework), you control the actual page <head> element via your framework — see "Static-site frameworks" below. To run experiments on Plasmic designs, see Experiments with Plasmic.

Ghost

Ghost Admin → Settings → Code Injection → Site Header. Paste the script tag.

Wix

Wix's "Custom Code" feature with "Place Code in: Head" sounds like head injection, but Wix actually runs custom code through its own JavaScript renderer — the code is injected after the page paints, same as a tag manager.

For Wix, only paste the script tag:

<script src="https://your-apex-domain/api/apex-js?key=YOUR_WORKSPACE_KEY" async></script>

Visitors will see a brief control flash on pages with running visual experiments before the variant applies. That's the trade-off Wix's architecture imposes, and every other experiment tool faces it on Wix too.

Google Tag Manager (or Segment, RudderStack, Tealium, Adobe Launch)

Tag managers inject content asynchronously, after the page paints. Put the <script> tag in a Custom HTML tag, trigger All Pages, and publish:

<script src="https://your-apex-domain/api/apex-js?key=YOUR_WORKSPACE_KEY" async></script>

If you run visual experiments and want the hiding style, it has to go directly into your site's HTML <head> (via your CMS or theme), never into the tag manager. Do not paste it into a Custom HTML tag: injected after paint, it hides a page the visitor is already looking at.

Static-site frameworks (Next.js, Astro, Remix, SvelteKit, Nuxt)

You're rendering <head> directly. Add the script tag to your root layout's head element:

// Next.js app/layout.tsx
export default function RootLayout({ children }) {
  return (
    <html>
      <head>
        <script src="https://your-apex-domain/api/apex-js?key=YOUR_WORKSPACE_KEY" async />
      </head>
      <body>{children}</body>
    </html>
  );
}

Same shape works for Astro's BaseLayout.astro, Nuxt's app.vue, and SvelteKit's +layout.svelte. On Next.js, code experiments can also assign on the server so the first HTML is already the variant, with no hiding needed — see Running experiments.

Don't see your platform?

If you can edit raw HTML in <head> (most platforms can), paste the script tag there. If you can only edit through a JavaScript-based code injection feature, treat it like Wix.

The Integrations page in your dashboard tracks install quality from real visitor heartbeats and surfaces a warning if your specific install is producing flicker — with a direct link back to the right section here.

What the Snippet Does Automatically

Once installed, the snippet runs on every page load and handles:

  • Visitor identity — Generates a persistent UUID stored in the apex_vid cookie (365-day expiry). This ID ties all sessions from the same browser together.
  • UTM capture — Reads utm_source, utm_medium, utm_campaign, utm_term, and utm_content from the URL and stores them two ways. apex_attr merges new values with existing ones so you never lose first-touch data, and apex_sattr holds only what the current visit arrived with. Traffic reports read the second one, so a visitor who returns directly counts as Direct rather than inheriting an old campaign.
  • Click ID capture — Captures ad platform click identifiers: gclid (Google Ads), fbclid (Meta Ads), li_fat_id (LinkedIn Ads), and msclkid (Microsoft Ads).
  • Pageview tracking — Sends a pageview event to /api/events with the current URL, referrer, and whether this is a landing page visit.
  • Experiment assignment — Fetches active experiments and applies variant changes to the DOM. See Experiments.
  • Form interception — Detects all <form> elements and injects hidden attribution fields on submit. See Form Tracking.
  • Engagement tracking — Measures time on page and scroll depth, reported when the visitor leaves.
  • Heartbeat — Sends a health signal with snippet version, screen size, viewport, language, and experiment assignments.

Verifying Installation

Open your site with DevTools

Navigate to any page where the snippet is installed and open Chrome DevTools (F12).

Check the Network tab

Filter by apex. You should see a request to /api/apex-js (the snippet itself) and subsequent requests to /api/experiments/active and /api/events.

Check cookies

In the Application tab under Cookies, look for apex_vid (your visitor UUID) and apex_attr (attribution JSON). Both confirm the snippet is running.

Verify from the dashboard

Open Set up Apex → Web Measurement. Apex waits for your site to say hello. You can also click Verify and enter your site URL. On Integrations, the Website card reads Heartbeats received once events are flowing.

Testing on staging without polluting production data

Drop the same snippet on your staging URL. Apex treats the website already on your workspace as Production. Localhost is Dev. Vendor preview platforms (Vercel, Netlify, and similar) are Beta. Any other host, including dev.company.com or qa.company.com, is Unclassified until you say otherwise. Those events stay out of your production dashboard slice. Like every event Apex ingests, they count toward the Events meter.

Where you installWhat Apex seesVisible in the Production dashboard slice?
The website on your workspace (acme.com, www.acme.com)ProductionYes
*.vercel.app, *.netlify.app, *.pages.devPreviewNo (flip the build pill to "Beta" or "All")
dev.acme.com, qa.acme.com, or any host we cannot proveUnclassifiedNo (flip the build pill to "All")
localhost:3000, 127.0.0.1, *.local, *.testLocalhostNo (flip the build pill to "Dev" or "All")

If your staging site is a host we cannot prove, name it. Open Set up Apex → Name your live and test sites and add the host under Beta. The snippet stays exactly the same. Apex files that site's traffic as Beta from then on, and you can name a site as Production the same way. Your AI agent can do it too with set_workspace_environments.

See Environments for how the buckets work and Test vs Production for billing and the mobile rules.

Hiding the page while visual experiments apply

Visual experiments change the page in the browser, which can only happen after the snippet runs. On a first visit, a visitor may briefly see the original version before the variant replaces it. If that flash matters for a test, add this style to your <head>, directly above the script tag:

<style id="apex-antiflicker">@keyframes apex-af{to{opacity:1}}html{opacity:0;animation:apex-af 0s 1.5s forwards}</style>
<script src="https://your-apex-domain/api/apex-js?key=YOUR_WORKSPACE_KEY" async></script>

Turn on Hide the page until visual experiments apply in the Set up Apex install step and the tag Apex gives you (and your AI agent) includes this line.

What it does:

  1. The style hides the page during HTML parse, before anything paints.
  2. The snippet reveals it as soon as it knows what to show. It removes the style right away on pages with no running experiment, applies a remembered variant without waiting on the network when it has one, and waits at most 1 second for the experiment list on a first visit.
  3. The page reveals itself after 1.5 seconds if the snippet never runs at all (an ad blocker, an offline phone, an outage). That reveal is built into the style, so no visitor is left on a blank page.

The cost is a short blank moment on first visits to pages with experiments. Leave it off unless you see the flash, and leave it off for code experiments, which don't need it.

Info

The snippet never hides pages you've listed as app screens in your workspace, or any page inside a native Capacitor app, and it takes the style down there if it finds one. See what the snippet does inside your app.

iOS Safari and Privacy Protections

iOS 17.4+ Safari includes Advanced Tracking and Fingerprinting Protection (ATFP) which classifies cross-origin scripts loaded from "tracker domains" and applies a few protections:

  • Cookies set under a cross-site context are capped at 7 days (ITP).
  • Some fingerprinting APIs return scrambled values.
  • A "you can reduce advanced privacy protections" warning bar may appear on pages where ATFP detects post-paint content shifts.

The snippet itself runs fine — experiments still execute, events still flow — but visitor identity is less stable across sessions and the warning bar dents trust.

If Safari traffic is meaningful for your business, set up a first-party snippet domain. Loading the snippet from your own subdomain (e.g. m.yourdomain.com) makes Safari treat it as first-party: the warning bar goes away, cookies persist for the full 365 days, Safari Private mode visitors stop being silently dropped, and data quality from iOS / iPadOS / macOS Safari users improves measurably.

For the most privacy-strict browsers (Brave, Firefox Strict, DuckDuckGo, iOS Lockdown Mode) where even first-party loading can be blocked, fire the high-stakes conversion events server-to-server from your backend using the Server Events API. The snippet handles everything the browser allows; the server fills the gaps. Both paths land in the same workspace and dedupe on event.id.

Cookies Reference

CookiePurposeExpiry
apex_vidPersistent anonymous visitor ID (UUID v4)365 days
apex_attrJSON-encoded UTM parameters and click IDs, merged across visits365 days
apex_sattrJSON-encoded UTM parameters and apex_link for the current visit only365 days
apex_sidSession ID, number, and start time. A session ends after 30 minutes of inactivity or when a new campaign arrives365 days

Every cookie uses SameSite=Lax and is scoped to the root path.

Cross-Subdomain Cookies

By default cookies are scoped to the exact host the snippet runs on. If your visitors traverse multiple subdomains (www.example.com → app.example.com) and you want one visitor identity across all of them, append &domain=registrable to the script src:

<script src="https://your-apex-domain/api/apex-js?key=YOUR_WORKSPACE_KEY&domain=registrable" async></script>

The snippet will then write cookies with Domain=.example.com so every subdomain sees the same apex_vid. The dashboard auto-includes this parameter when you have an active first-party snippet domain.

Next Steps