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 projectKey for staging, preview, production, mobile builds, dev, TestFlight, App Store, Play closed tracks — every environment. Apex auto-detects what's billable (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 these two lines in your site's <head>. Replace YOUR_PROJECT_KEY with the key from your Apex dashboard (Settings > Project Key).
<style id="apex-antiflicker">html{opacity:0!important;transition:opacity .12s}</style>
<script src="https://your-apex-domain/api/apex-js?key=YOUR_PROJECT_KEY" async></script>
The snippet recognises the inline <style id="apex-antiflicker"> and removes it as soon as experiments are applied, so your visitors never see the original content flash before the variant. The async attribute lets the browser download the script in parallel with HTML parsing — important on slow networks where defer is too late to prevent the first paint.
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_PROJECT_KEY" async></script>
Installing on a Site Builder or via a Tag Manager
The two-line install above (anti-flicker stub + script) only works when the inline <style> runs synchronously during HTML parse, before the body paints. Whether that's possible depends on where you're installing:
- Synchronous head injection works on WordPress, Webflow, Squarespace (Business plan and up), Shopify, Framer, Ghost, and any platform with raw HTML access. Use the standard two-line install as-is.
- Asynchronous injection only 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. The inline stub does more harm than good here because it gets applied after the page has already painted, retroactively hiding what was already on screen. Only install the script tag on these platforms — the snippet detects the post-paint scenario automatically and skips the stub.
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). Search for apex-antiflicker. If it's between <head> and </head>, your platform supports synchronous injection. If it's anywhere else (typically inside a <div> in the body), it's asynchronous and you should remove the stub.
WordPress
You have three options. From most to least technical:
- Edit your theme's
header.php(or use a child theme) and paste the two-line install just before</head>. - Use a head-injection plugin like Insert Headers and Footers or WPCode. These render their settings server-side into the
<head>, so synchronous injection works. Paste the full two-line install in the "Header" section. - Use Site Kit / Tag Manager if you're already managing tags through GA. Install only the
<script>tag here — paste the inline<style>directly into your theme separately (option 1 or 2).
Webflow
Project Settings → Custom Code → Head Code. Paste the full two-line install. Webflow renders custom code server-side into the head, so the <style> runs synchronously and the install works as documented.
Squarespace
Settings → Advanced → Code Injection → Header. Paste the full two-line install. 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 two-line install just before </head>. Avoid pasting the inline <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 full two-line install. Framer renders custom code into the static HTML head, so the stub runs synchronously.
Plasmic
Plasmic's hosted sites are the awkward case here, similar to Wix. Plasmic Studio's "Custom Code → Head" panel sounds like synchronous head injection, but it actually renders the code into a body-level component on the published page — so an inline <style> placed there gets applied after the body has already started rendering and retroactively hides the visible content.
For Plasmic hosted sites, install only the <script> tag — leave the inline <style> out:
<script src="https://your-apex-domain/api/apex-js?key=YOUR_PROJECT_KEY" async></script>
Visitors will see a brief content swap on pages with running experiments before the variant applies. Same trade-off as Wix — it's a Plasmic architecture limitation that affects every A/B testing tool the same way. Don't paste the inline stub into Plasmic; it will blank your entire page.
For Plasmic code-export to Next.js (or another framework), you control the actual page <head> element via your framework. Use the standard two-line install in your root layout — see "Static-site frameworks" below.
Ghost
Ghost Admin → Settings → Code Injection → Site Header. Paste the full two-line install.
Wix
Wix is the awkward case. Wix's "Custom Code" feature with "Place Code in: Head" sounds like sync injection, but Wix actually runs custom code through its own JavaScript renderer — the code is injected after the page paints, same problem as a tag manager.
For Wix, only paste the script tag — leave the inline <style> out:
<script src="https://your-apex-domain/api/apex-js?key=YOUR_PROJECT_KEY" async></script>
Visitors will see a brief control flash on pages with running experiments before the variant applies. That's the trade-off Wix's architecture imposes; it's the same trade-off every other A/B testing tool faces on Wix. The snippet detects Wix and refuses to fight the architecture, so you'll never see the dreaded blank-screen flash — just a brief content swap.
Google Tag Manager (or Segment, RudderStack, Tealium, Adobe Launch)
Tag managers inject content asynchronously, after the page paints. Same constraint as Wix.
Two ways to install correctly. Pick the first one if at all possible:
Option 1 — Split install (no flicker): paste the inline <style> directly into your site's HTML <head> (via your CMS, theme, etc.), and put the <script> tag in your tag manager:
<!-- Step 1: in your site's <head>, BEFORE any other scripts -->
<style id="apex-antiflicker">html{opacity:0!important;transition:opacity .12s}</style>
<!-- Step 2: in your tag manager's Custom HTML tag -->
<script src="https://your-apex-domain/api/apex-js?key=YOUR_PROJECT_KEY" async></script>
Option 2 — Tag-manager-only (brief flicker on pages with experiments): if you can only edit through the tag manager, install only the <script> tag — leave the inline <style> out entirely:
<script src="https://your-apex-domain/api/apex-js?key=YOUR_PROJECT_KEY" async></script>
Do not paste both lines into a single Custom HTML tag in GTM. The <style> injected post-paint would visibly blank the already-rendered page — that's worse than no anti-flicker at all. The Integrations page in your dashboard surfaces a "Your install is producing flicker" warning when it detects this pattern, with a link back to this section.
Static-site frameworks (Next.js, Astro, Remix, SvelteKit, Nuxt)
You're rendering <head> directly. Paste the two-line install in your root layout's head element:
// Next.js app/layout.tsx
export default function RootLayout({ children }) {
return (
<html>
<head>
<style id="apex-antiflicker" dangerouslySetInnerHTML={{ __html: "html{opacity:0!important;transition:opacity .12s}" }} />
<script src="https://your-apex-domain/api/apex-js?key=YOUR_PROJECT_KEY" async />
</head>
<body>{children}</body>
</html>
);
}
Same shape works for Astro's BaseLayout.astro, Nuxt's app.vue, and SvelteKit's +layout.svelte.
Don't see your platform?
If you can edit raw HTML in <head> (most platforms can), the standard two-line install works. If you can only edit through a JavaScript-based code injection feature, treat it like Wix — install only the <script> tag. The snippet auto-detects the install context and adapts.
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_vidcookie (365-day expiry). This ID ties all sessions from the same browser together. - UTM capture — Reads
utm_source,utm_medium,utm_campaign,utm_term, andutm_contentfrom the URL and stores them in theapex_attrcookie. New UTM values merge with existing ones so you never lose first-touch data. - Click ID capture — Captures ad platform click identifiers:
gclid(Google Ads),fbclid(Meta Ads),li_fat_id(LinkedIn Ads), andmsclkid(Microsoft Ads). - Pageview tracking — Sends a
pageviewevent to/api/eventswith 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
Go to Integrations in the Apex dashboard and click Verify Installation. Enter your site URL — Apex will confirm it detects the snippet.
Testing on staging without polluting production data
Drop the same snippet on your staging URL. Apex auto-detects the hostname and labels staging traffic as a preview environment — those events are free (don't count toward billing) and hidden from your default dashboard.
| Where you install | What Apex sees | Billable? | Visible in default dashboard? |
|---|---|---|---|
acme.com, www.acme.com, app.acme.com | Production | Yes | Yes |
staging.acme.com, preview.acme.com, dev.acme.com | Preview | No | No (flip the build pill to "Beta" or "All") |
acme-pr-42.vercel.app, *.netlify.app, *.pages.dev | Preview | No | No |
localhost:3000, 127.0.0.1, *.local, *.test | Localhost | No | No (flip the build pill to "Dev" or "All") |
Verify staging is bucketed correctly at Settings → Workspace → Web environments — you should see a row labelled "Preview" appear within ~30 seconds of the first staging event.
If your staging URL doesn't match any of the standard patterns (e.g. you use a custom domain like app-staging.acme.com that resolves to your staging stack), force the bucket with an explicit override:
<script>
(function() {
var s = document.createElement('script');
s.src = 'https://cdn.apex.inc/apex.js';
s.async = true;
s.onload = function() {
window.apex.init({
projectKey: 'apx_live_...',
// Force preview classification on a hostname that looks like production.
environment: 'preview',
});
};
document.head.appendChild(s);
})();
</script>
See Test vs Production for the full classification rules, billing implications, and Apex Skill recipe.
Anti-Flicker: How It Works
A/B testing tools have a fundamental timing problem: variant DOM changes can only happen after the snippet runs, which means visitors briefly see the original content before the swap. Apex addresses this with a two-line install:
- The inline
<style>— runs synchronously during HTML parse, hiding<html>before the body paints. This is the only reliable way to prevent first-paint flicker;deferand most async loaders run too late. - The async
<script>— downloads in parallel with HTML, executes as soon as ready, applies experiment changes, then removes the inline<style>so the page becomes visible.
The snippet is smart about when to keep the page hidden:
- If your visitor has cached experiments from a previous page (5-minute localStorage cache) and none target the current URL, the snippet removes the stub immediately on script execution — typically within 50 ms. Pages with no experiment running don't pay any anti-flicker cost.
- If a cached experiment matches the page, the snippet applies the cached variant optimistically and removes the stub. No fetch round-trip in the visible-rendering path.
- First-ever visit (no cache yet) hides until the experiment fetch completes, with a 1-second safety ceiling so a slow or failed fetch never strands the visitor on a blank page. A brief control-flash is much better than a multi-second blank screen.
Info
Anti-flicker is automatically disabled on /dashboard and /onboarding paths to avoid affecting the Apex app itself.
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 iOS 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, and data quality from iOS users improves measurably.
Cookies Reference
| Cookie | Purpose | Expiry |
|---|---|---|
apex_vid | Persistent anonymous visitor ID (UUID v4) | 365 days |
apex_attr | JSON-encoded UTM parameters and click IDs | 365 days |
Both cookies use SameSite=Lax and are 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_PROJECT_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
- First-party snippet domain — set up CNAME loading for full iOS Safari compatibility
- Client-side experiments — how variant assignment and DOM changes work
- Form tracking — automatic form interception and identity stitching
- Custom events — sending events via the
window.apexAPI