The names
Legacy aliases exist for backwards compatibility (
EXP_ID,
VARIANT_KEY), do not rely on them for new code.
The order
If you inspect the compiled variant function, the parameters land at specific positions. The exp key isarguments[4]. This ordering is
stable, a new binding at a later position does not shift existing
ones.
Rule of thumb: reference the bindings by name, not by position.
The names are stable public API; the positions are an implementation
detail.
Using them
Inside variant JS:Why the key and not just the UUID
The UUID is stable but not human-readable. The key is short, human-readable, and immutable, cloning an experiment mints a new key so referencing by key does not silently target the wrong test. If your goal is “log which variant applied”, the key is what a human reading logs wants to see. If your goal is “call an internal API that stores per-experiment state”, either works, the UUID is a slightly better primary key because it is opaque and cannot be confused with anything else.What is not in scope
this. The variant function’sthisis set to the runtime’s internal apply context, notwindow. Reach forwindowexplicitly if you need it.- Any DOM helper. Use
document.querySelector,abtestly.waitFor, or your own libraries, the runtime does not inject jQuery-shaped shortcuts into scope. - A framework’s runtime. React, Vue, and friends are not auto-imported. If your variant needs a framework, load it via Dev libraries or from the page itself.
onApply callback signature
If you use abtestly.onApply(...), the callback receives an
ApplyContext object with:
experimentId, the UUID, same as the binding above. The short key is not on the context, use theABTESTLY_EXP_KEYbinding.variantKey, same as the binding.routeRevision, a monotonic counter that ticks on every SPA route change. Compare against it inside an async apply to detect supersession:
onCleanup, undoing what you applied
abtestly.onCleanup(experimentIdOrKey, fn) registers the teardown
half of a variant. The first argument accepts either the UUID or the
short key, same as onApply. The callback takes no arguments,
there is no ApplyContext on this side, so anything you need at
teardown time you close over when you register.
When it fires. SPA route changes, and nothing else. A route
change means pushState, replaceState, popstate, or hashchange
landing on a URL that differs from the current one. There is no
teardown on tab close or pagehide, and none when an experiment is
paused, archived, or unpublished mid-session, those only take effect
on the visitor’s next full page load.
On each route change the runtime fires onCleanup for every
experiment that was active on the previous route, including the ones
still active on the new route. That is deliberate: those experiments
are about to have their onApply callbacks fire again, and cleaning
up first is what stops listeners and injected nodes stacking. Do not
read a cleanup as “this experiment is over”.
Handlers fire in registration order, first registered, first to run.
What the runtime removes for you. One thing: the
<style data-abtestly-exp="<id>"> tag holding the variant’s CSS
field, and only when the experiment has truly deactivated, meaning it
was eligible on the old route and is not eligible on the new one. An
experiment that stays eligible has its tag re-stamped instead. That
is the whole of the automatic teardown.
What stays yours.
Variant JavaScript does not re-run on a route change, only your
registered callbacks do. This is what makes top-level setup a bug
rather than a matter of taste. Code at the top of your variant JS
runs exactly once, on the initial page load, so it never gets a
second chance against the DOM your framework rendered for the next
route, and nothing the runtime does will put it back.
Before, the listener is added again every time the visitor navigates
back to a tested route, so on a persistent element one click fires
the handler twice, then three times:
Stability
The three canonical names,ABTESTLY_EXP_ID, ABTESTLY_EXP_KEY,
ABTESTLY_VARIANT_KEY, are stable public API. They do not change
without a major version bump. Legacy aliases stay for at least one
major version.