/Docs

Project JavaScript

Project JavaScript is a block of code you write once that runs on every page load, before any experiments are evaluated. Use it for global setup that all your experiments share.

Where to write it

Go to Project Settings → Snippet. You will see a code editor section labelled Project JavaScript. Write your code there and click Publish. Changes take effect the next time the datafile is published.

The initProject wrapper

Your Project JavaScript must be wrapped in a function named initProject that receives a single options argument:

Required wrapper function signature
javascript
1function initProject(options) {
2 // Your global setup code here
3}

The editor blocks saving if the wrapper function is missing or renamed. The snippet calls initProject(options) automatically on every page load and on every SPA navigation before experiments evaluate.

The options object

The options argument provides project context, a teardown hook, self-cleaning helpers, and tracking utilities. Because initProject runs on every page load and SPA navigation — it is not gated by an activation event — setup code goes directly in the function body.

Context properties

NameDescription
options.projectIdstringOptional

The unique identifier for the current project.

Lifecycle hooks

NameDescription
options.onRemove(callback: () => void) => voidOptional

Register a callback to run before Project JS is torn down on the next SPA navigation. This fires before each re-run, giving you a chance to clean up anything from the previous page before setup runs again for the new page.

Self-cleaning helpers

Anything started through these helpers is automatically torn down when options.onRemove fires (i.e. before each SPA navigation re-run).

NameDescription
options.waitUntil(conditionFn, callback, opts?) => handleOptional

Polls a condition function and calls the callback with the truthy result when the condition is met. Automatically cancelled on teardown.

options.setTimeout(fn: () => void, ms?: number) => handleOptional

Like window.setTimeout, but automatically cleared on teardown.

options.setInterval(fn: () => void, ms?: number) => handleOptional

Like window.setInterval, but automatically cleared on teardown.

options.addEventListener(target, type, handler, opts?) => voidOptional

Like target.addEventListener, but automatically removed on teardown.

Tracking utilities

NameDescription
options.track.event(shortId: number, properties?: { revenue?: number }) => voidOptional

Manually fire a custom metric event for this visitor.

options.track.segment(segmentKey: string, segmentValue: string) => voidOptional

Send a segment value for this visitor.

options.getVisitorId() => stringOptional

Returns the current visitor's unique identifier.

options.getVariation(experimentId: string) => string | nullOptional

Returns the variation ID assigned to this visitor for a given experiment, or null if not assigned.

options.getActiveExperiments() => Array<{ experimentId: string; variationId: string }>Optional

Returns all experiments the current visitor is currently active in.

options.forceVariation(experimentId: string, variationId: string) => booleanOptional

Forces the current visitor into a specific variation of an experiment. Returns true if the variation was applied successfully.

When it runs

Project JS runs after the window.avsb API is ready but before any experiments are evaluated. The page is still hidden (anti-flicker is active), so anything you set up here is available to your experiments without causing a visual flash.

On single-page applications, initProject re-runs on every client-side navigation. Before each re-run, the snippet fires options.onRemove and tears down all resources started via the self-cleaning helpers from the previous run. This means you do not need to guard against duplicate listeners — the previous run is fully cleaned up before initProject is called again.

What you can do with it

Set custom segments

If your experiments target specific audiences (for example, logged-in users or premium subscribers), use Project JS to tag visitors with custom segments that the audience evaluator can use.

Setting visitor segments
javascript
1function initProject(options) {
2 // initProject runs on every page load — apply setup directly in the body
3 if (window.currentUser) {
4 options.track.segment('plan', window.currentUser.plan);
5 options.track.segment('userTier', window.currentUser.tier);
6 }
7}

Track global custom events

Set up event listeners that fire across all experiments — useful for tracking conversions that are not tied to a single experiment. Use options.addEventListener so the listeners are automatically removed before each re-run on SPA navigation.

Global event tracking
javascript
1function initProject(options) {
2 const form = document.querySelector('#newsletter-form');
3 if (form) {
4 // options.addEventListener is automatically removed before each re-run
5 options.addEventListener(form, 'submit', () => {
6 // 12345 is the metric's short ID
7 options.track.event(12345);
8 });
9 }
10}

Shared helper functions

Define utilities on window that your variation code can reuse, so you do not repeat the same code in every variation. Register cleanup with options.onRemove to avoid stale references across navigations.

Shared helpers
javascript
1function initProject(options) {
2 // Expose a helper to all variation JS
3 window.myHelpers = {
4 formatCurrency(value) {
5 return new Intl.NumberFormat('en-GB', { style: 'currency', currency: 'GBP' }).format(value);
6 }
7 };
8
9 options.onRemove(() => {
10 delete window.myHelpers;
11 });
12}

Initialize third-party integrations

If your variation code depends on a third-party library or SDK, you can configure it in Project JS so it is ready by the time experiments run.

Integration setup
javascript
1function initProject(options) {
2 window.avsb.onEvent = function(event) {
3 if (typeof gtag !== 'undefined') {
4 gtag('event', 'experiment_impression', {
5 experiment_id: event.experiment_id,
6 variant_id: event.variation_id
7 });
8 }
9 };
10}
SPA navigation — no more duplicate guards
Because initProject gets a fresh options context on every navigation and teardown happens automatically before each re-run, you no longer need to check a “already set up” flag to prevent duplicate listeners. Just use the self-cleaning helpers and let A vs B manage the lifecycle.

Editor linting

The Project JS editor has a linting control in its toolbar with three levels. It applies to this project’s code only — each experiment carries its own separate setting.

  • Off — the editor does no checking and shows no underlines.
  • On (the default) — the editor underlines type and syntax problems as you type. This is advisory; you can still publish.
  • Strict — turns on stricter TypeScript checks (unused variables, possible null access, missing types) and blocks publishing until the reported type errors are fixed. The publish attempt returns the list of errors to resolve.

Linting checks are only available for TypeScript projects. JavaScript projects still get basic syntax validation but no type checking.

Shared types

TypeScript projects get a shared.d.ts file alongside project.ts. Anything you declare there — interfaces and type aliases — is automatically available in your Project JS andin every experiment’s trigger and variation files across the project, with no import needed.

shared.d.ts — define once, use everywhere
typescript
1interface CurrentUser {
2 id: string;
3 plan: 'free' | 'pro' | 'enterprise';
4}
Types only — nothing ships
The shared types file holds type declarations only. Types are erased when your code is compiled to JavaScript, so this file adds nothing to what runs in your visitors’ browsers — it exists purely to help you while writing code.