/Docs

Triggers

By default, a variation is injected immediately after a visitor is bucketed into it. Triggers let you override this behaviour and control exactly when — and under what conditions — the variation is applied and removed.

Warning
Exposure is only counted when activate() is called. If your trigger never calls activate(), the visitor will not be counted as exposed to the experiment. This means your trigger directly controls when impressions are recorded.

What is a trigger?

A trigger is a JavaScript function named initTrigger that you write as part of an experiment. The A vs B snippet calls your function automatically and passes three arguments:

  1. options — an object with experiment context, lifecycle hooks, self-cleaning helpers, and tracking utilities.
  2. activate — a function you call to apply the variation and count the exposure.
  3. deactivate — a function you call to remove the variation.

Without a trigger, the variation is activated (and exposure counted) immediately. With a trigger, you have full control. Common use cases:

  • Wait for a specific DOM element to appear before applying the variation (important for SPAs and lazy-loaded content).
  • Only activate when the visitor scrolls past a certain point on the page.
  • Activate after a user interaction (clicking a button, focusing an input).
  • Deactivate the variation when the visitor navigates to a different section of the page.

Function signature

Your trigger function must be named initTrigger and accept three parameters:

Trigger function signature
javascript
1function initTrigger(options, activate, deactivate) {
2 // Your logic here — call activate() when ready
3}

The snippet wraps your code and calls it like this internally:

How the snippet invokes your trigger
javascript
1// Simplified — this is what the snippet does behind the scenes
2initTrigger(options, activate, deactivate)
Warning
The function must be named initTrigger. The editor will block saving if the wrapper function is missing or named anything else (including the old name triggers, which is no longer supported).

activate() and deactivate()

These two functions are passed as the second and third arguments to your trigger. They are not properties on the options object.

NameDescription
activate() => voidOptional

Applies the variation (CSS + JS) and counts the exposure. Can only fire once — subsequent calls are ignored. This is the only way an exposure is recorded when a trigger is present.

deactivate() => voidOptional

Removes the variation CSS/JS and runs all cleanup registered via options.onRemove. Useful for SPAs where conditions may change on navigation.

Warning
activate() can only be called once per experiment evaluation. After activate() or deactivate() has been called, calling activate() again has no effect.

The options object

The first argument to your trigger function is an options object containing experiment context, lifecycle hooks, self-cleaning helpers, and tracking utilities.

Context properties

NameDescription
options.experimentIdstringOptional

The unique identifier for the experiment.

options.experimentNamestringOptional

The display name of the experiment.

options.variationIdstringOptional

The unique identifier for the variation assigned to this visitor.

options.variationNamestringOptional

The display name of the variation (e.g. "Control", "Variant 1").

options.variationType'control' | 'variant'Optional

Whether this visitor was assigned the control or a variant.

Lifecycle hooks

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

Register a callback to run immediately after activate() applies the variation. If activate() has already been called, the callback fires immediately.

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

Register a callback to run when the variation is removed — either because deactivate() was called or because A vs B tears down the experiment (e.g. on SPA navigation). Use for cleanup that cannot be handled by the self-cleaning helpers.

Self-cleaning helpers

These helpers work like their native browser equivalents, but anything you start through them is automatically torn down when the variation is removed. You do not need to manually cancel them in options.onRemove.

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

Polls a condition function and calls the callback with the truthy result when the condition is met. Uses a MutationObserver + 100ms polling interval. Automatically cancelled on variation removal. Returns a handle with a cancel() method. See the waitUntil section below for full details.

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

Like window.setTimeout, but the timer is automatically cleared when the variation is removed.

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

Like window.setInterval, but the interval is automatically cleared when the variation is removed.

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

Like target.addEventListener, but the listener is automatically removed when the variation is removed.

Tracking utilities

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

Manually fire a custom metric event for this visitor. The shortId must match a metric configured in your project.

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 the list of all experiments the current visitor is currently active in, as an array of { experimentId, variationId } pairs.

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

Forces the current visitor into a specific variation of another experiment. Returns true if the variation was applied successfully. Primarily useful for QA and multi-experiment coordination.

Basic trigger example

The simplest trigger just calls activate() immediately, which is equivalent to having no trigger at all:

Immediate activation (same as no trigger)
javascript
1function initTrigger(options, activate, deactivate) {
2 activate();
3}

Waiting for a DOM element

This is the most common use case. If the element you want to modify is not in the DOM when the experiment evaluates (for example, it is rendered by a JavaScript framework after the initial page load), use options.waitUntil to delay activation until it appears. Because it is started via options, it is automatically cancelled if the variation is torn down before the element arrives.

Wait for element before activating
javascript
1function initTrigger(options, activate, deactivate) {
2 // waitUntil takes a condition function that returns a truthy
3 // value when the condition is met
4 options.waitUntil(
5 () => document.querySelector('#hero-section'),
6 () => {
7 // The element exists — safe to activate
8 activate();
9 }
10 );
11}
Info
options.waitUntil uses a MutationObserver and a 100ms polling interval internally. The condition function is called on every DOM mutation and every 100ms. If the condition never returns a truthy value, the variation is never activated — the visitor sees the control and no exposure is counted.

You can also set a timeout to stop waiting after a certain period:

waitUntil with timeout
javascript
1function initTrigger(options, activate, deactivate) {
2 options.waitUntil(
3 () => document.querySelector('#hero-section'),
4 () => activate(),
5 { timeout: 5000 } // Give up after 5 seconds
6 );
7}

options.waitUntil is not limited to DOM elements. You can wait for any condition — a global variable, a data attribute, or a computed value:

Wait for a global variable
javascript
1function initTrigger(options, activate, deactivate) {
2 options.waitUntil(
3 () => window.appState?.isReady,
4 () => activate()
5 );
6}

Activating on scroll

Use options.addEventListener instead of bare window.addEventListener — the listener is automatically removed when the variation is torn down.

Activate when user scrolls 50% down the page
javascript
1function initTrigger(options, activate, deactivate) {
2 options.addEventListener(window, 'scroll', function onScroll() {
3 const scrollPercent =
4 window.scrollY / (document.body.scrollHeight - window.innerHeight);
5 if (scrollPercent >= 0.5) {
6 activate();
7 }
8 }, { passive: true });
9}

Activating on user interaction

Activate when user clicks a specific button
javascript
1function initTrigger(options, activate, deactivate) {
2 options.waitUntil(
3 () => document.querySelector('#upgrade-btn'),
4 (button) => {
5 // waitUntil passes the truthy result to the callback
6 options.addEventListener(button, 'click', () => activate(), { once: true });
7 }
8 );
9}

Manual cleanup with onRemove

The self-cleaning helpers (options.waitUntil, options.setTimeout, options.setInterval, options.addEventListener) handle their own teardown automatically. Use options.onRemove for anything that falls outside those helpers — for example, a native IntersectionObserver or a third-party subscription.

Manual cleanup with IntersectionObserver
javascript
1function initTrigger(options, activate, deactivate) {
2 const observer = new IntersectionObserver((entries) => {
3 if (entries[0].isIntersecting) {
4 observer.disconnect();
5 activate();
6 }
7 });
8
9 const target = document.querySelector('#pricing-section');
10 if (target) {
11 observer.observe(target);
12 }
13
14 // IntersectionObserver is not a self-cleaning helper, so register cleanup manually
15 options.onRemove(() => {
16 observer.disconnect();
17 });
18}

How exposure tracking works

Understanding when exposures are counted is critical for accurate experiment results:

  • Without a trigger: The visitor is bucketed, the variation is applied, and the exposure is counted — all immediately.
  • With a trigger: The visitor is bucketed immediately, but the variation is not applied and the exposure is not counted until your trigger calls activate().
  • If activate() is never called, the visitor sees the original page (control experience) and is not counted in the experiment results at all.
Tip
This behaviour is intentional. It prevents inflating your sample size with visitors who never actually saw the experiment — for example, visitors who leave the page before scrolling to the tested section.

What happens without a trigger

When an experiment has no trigger code, the snippet follows this flow:

  1. Visitor lands on a matching page (targeting rules pass).
  2. Visitor is bucketed into a variation based on traffic allocation.
  3. Variation CSS and JS are injected immediately.
  4. An exposure event is sent immediately.

Adding a trigger inserts a gate between steps 2 and 3 — the variation is not injected until you explicitly call activate().

Where to write trigger code

Trigger code is written in the experiment builder on the Variations step (Step 2). Trigger code is optional— if you don't add any, the variation is applied and the exposure is counted immediately when the visitor is bucketed. If you do add trigger code, the editor requires it to be wrapped in an initTrigger(options, activate, deactivate) function. The snippet calls this function and waits for you to call activate() before injecting the variation or counting the exposure.