> ## Documentation Index
> Fetch the complete documentation index at: https://docs.abtestly.com/llms.txt
> Use this file to discover all available pages before exploring further.

# window.abtestly API

> Every method and event on the runtime object. Stable surface, you can call these from variant JS or from your own scripts.

Once the snippet has loaded, `window.abtestly` (and its alias
`window.__abtestly`) is available on the page. This is the runtime's
public surface. It is designed to be called from variant JavaScript, from
your own scripts, and, for `debug`, from the DevTools console.

<Note>
  Writing the variant code itself? The dashboard's [variation
  editor](/build/variations) is Monaco (VS Code) with linting and
  Cmd+S save, and you can [attach curated
  libraries](/build/dev-libraries) like Splide or GLightbox from a
  picker instead of pasting `<script>` tags into every variant.
</Note>

## The methods

### `trackGoal(key, valueOrOpts?)`

Fire a custom goal.

```js theme={null}
// simple
abtestly.trackGoal('newsletter_signup');

// with revenue (number sugar)
abtestly.trackGoal('purchase', 129.00);

// full form for revenue with dedup and refunds
abtestly.trackGoal('purchase', {
  value: 129.00,
  orderId: 'order-9182',
  currency: 'USD',
});

// refund
abtestly.trackGoal('purchase', {
  refund: true,
  refundId: 'refund-3341',
  orderId: 'order-9182',
});
```

**Returns:** `boolean`. `true` if accepted, `false` if the visitor was
opted-out or the goal key is not one your experiments care about.

`orderId` enables revenue deduplication. If two beacons arrive with the
same `expId:goalId:orderId`, the second is netted server-side so a
double-fire from a retry does not double-count revenue.

### `onApply(experimentIdOrKey, fn)`

Register a callback that fires when a specific experiment applies. If the
experiment already applied on this pageview, the callback fires
immediately.

```js theme={null}
abtestly.onApply('checkout-simplify', (ctx) => {
  console.log('checkout-simplify applied for', ctx.variantKey);
});
```

The callback receives an `ApplyContext` with the variant key and a
route revision.

### `onCleanup(experimentIdOrKey, fn)`

Register a callback that fires when the experiment is torn down, usually
on SPA route change to a page the experiment does not match. Cleanups
fire in reverse order of registration (LIFO).

```js theme={null}
abtestly.onCleanup('checkout-simplify', () => {
  // undo any DOM mutations you made in onApply
  document.querySelector('.mount').remove();
});
```

### `waitFor(target, cb, timeoutMs = 5000)`

Wait for an element or a predicate. Polls at 50 ms. Fires `cb` when the
condition is met, or times out silently after `timeoutMs`.

```js theme={null}
// single selector
abtestly.waitFor('.cart-total', (el) => {
  el.textContent = 'You save $X';
});

// array of selectors — cb fires once all resolve
abtestly.waitFor(['.header', '.footer'], ([h, f]) => { … });

// custom predicate
abtestly.waitFor(() => window.__ready === true, () => { … });
```

### `activate(key)`

Fire a **manual** activation trigger. See
[Activation triggers](/targeting/activation-triggers) for when to use
this.

```js theme={null}
const result = abtestly.activate('modal-copy-test');
// 'activated' | 'already_active' | 'unknown_experiment' |
// 'not_eligible' | 'not_bucketed' | 'wrong_activation_mode'
```

### `dev.apply(experimentId)`

**Development only.** Apply an experiment's variant to the current
pageview without bucketing and without firing a beacon. Useful for
prototyping. Do not ship code that calls this.

```js theme={null}
abtestly.dev.apply('checkout-simplify');
```

### `debug(opts?)`

Return a snapshot of what the runtime knows: loaded experiments, the
last handful of events (buckets picked, variants applied, beacons sent),
and any errors caught. See [Dev debugger](/preview/dev-debugger) for the
full shape.

```js theme={null}
abtestly.debug();          // human-readable
abtestly.debug({ json: true });  // JSON, for pasting into an issue
```

## Events

### `abtestly:routechange`

Fires on `window` whenever ABTestly detects an SPA route change and
finishes re-evaluating. The `detail` object includes:

* `url`, the new URL
* `activeExperiments`, experiment ids currently applied on the new
  route
* `deactivatedExperiments`, experiment ids that were torn down

```js theme={null}
window.addEventListener('abtestly:routechange', (e) => {
  console.log('now on', e.detail.url);
});
```

## Runtime bindings inside variant JS

Every variant's JavaScript runs inside a function that gets a few names
passed in as arguments. In addition to the raw code you wrote, you can
reference:

* `ABTESTLY_EXP_KEY`, the experiment's short code (e.g. `checkout-simplify`)
* `ABTESTLY_EXP_ID`, the experiment's UUID
* `ABTESTLY_VARIANT_KEY`, the variant's short key (`control`, `variant-1`, …)

These are useful for logging or for keying your own storage per-variant.
See [runtime bindings](/developer/runtime-bindings) for the full list
and the exact positions.

## Stability

Everything on this page is part of the **stable public API**. Method
signatures do not change without a major version bump; deprecated
methods stick around for at least one major cycle after their
replacement lands.

Internals not documented here, `_handlers`, `_pendingActivations`,
private queues, are exactly that: internal. If a piece of your code
reaches for one of those, expect it to break.

## Related

<CardGroup cols={2}>
  <Card title="Variation editor (Monaco)" icon="code" href="/build/variations">
    Real editor, real lints. Cmd+S save, fullscreen, theme & font.
  </Card>

  <Card title="Dev libraries" icon="cubes" href="/build/dev-libraries">
    Curated Splide, GLightbox, and friends via a picker.
  </Card>

  <Card title="Dev debugger" icon="bug" href="/preview/dev-debugger">
    Console snapshot of assignments, applies, beacons, errors.
  </Card>

  <Card title="Runtime bindings" icon="link" href="/developer/runtime-bindings">
    ABTESTLY\_EXP\_KEY and friends inside variant code.
  </Card>
</CardGroup>
