Skip to main content
When something in an experiment is not doing what you expect, the first thing to reach for is the debugger. It runs entirely in the browser and requires no dashboard access.

Call it

Open DevTools on the site with the snippet installed:
Or, for a compact JSON blob you can paste into a bug report:
The same object is also reachable as window.abtestly.

What it shows

  • Session. Snippet version, site id, domain, SPA mode, whether GA4 is enabled, which product analytics adapters are on, the consent mode, and whether this page load is live or preview.
  • Loaded experiments. Every experiment active on this page, with the variant assigned.
  • Registered goals. Every goal the snippet registered, with its selector or condition, its state, and how many times it has fired this page load.
  • Event log. A ring buffer of the last 100 events the runtime observed. Older events are dropped and the count of dropped events is reported.

What the debugger does not prove

The debugger observes the snippet inside your browser. That is a real and useful record of what the snippet decided and attempted, but it is not confirmation that anything reached your results. Three states are distinct and worth keeping apart:
  • Emitted. The snippet built an event and handed it to the browser. A beacon event in the log with wouldSend: true means exactly this.
  • Sent. A transport actually accepted it. The debugger records serverStatus: 'unknown' on a normal live send, because the beacon is fire and forget and its network outcome is never awaited.
  • Accepted and recorded. The ingest endpoint validated the payload, it survived filters such as blocked IPs and visitor opt out, and it was written to your results. The debugger never observes this.
So “the goal fired” in the debugger means the snippet’s handler ran, not that a conversion landed in your report. To confirm the latter, check the experiment’s results.

Sensitive fields

The debugger masks a narrow set of fields: orderId and refundId inside a goal event’s payload. They are shown as a short prefix and the last four characters rather than in full. Masking is lifted when you pass includeSensitive: true and one of these is true:
  • The page is in preview mode.
  • You have set the opt in explicitly: localStorage.setItem('abtestly_debug_sensitive', '1').
This gate is not access control, and it does not make a debug blob safe to share. Anyone who can open DevTools on the page can set that localStorage key themselves, and everything outside orderId and refundId is never masked at all. A debug blob contains your site id, your domain, your experiment and variant names, every goal selector and URL pattern, the page path, and the browser’s user agent.Treat the output as internal. Read it before pasting it into a support ticket or a public issue, and do not put anything confidential into variation code, goal selectors, experiment names, or error payloads on the assumption that the debugger will hide it.

dev.apply

For prototyping:
Treats the experiment as active for the rest of the tab session, so your onApply callbacks fire and SPA navigations re cycle the lifecycle. It does not bucket the visitor, does not fire a beacon, and does not inject the variation’s CSS or JS from the dashboard. It is a flag flip for the lifecycle hooks, meant for iterating on variant code locally.
abtestly.dev.* is for the console and userscripts only. Do not leave it in variation code; the dashboard editor will flag it.

Event kinds in the buffer

  • config_loaded, snippet fetched config.
  • consent_granted, consent flipped to granted.
  • assignment, visitor was bucketed (or not).
  • variant_applied, variant JS/CSS applied.
  • variant_apply_error, variant code threw. Includes message and stack.
  • spa_route_change, SPA navigation handled.
  • spa_cleanup, variant cleanup ran on route change.
  • spa_reapply, variant re applied on a new matching route.
  • spa_stale_cancelled, an async apply detected supersession and bailed.
  • activation_view_armed, IntersectionObserver installed for a When visible trigger.
  • preview_token_rejected, a preview link’s token was refused, with the reason. Most often the token expired or preview links were revoked.
  • beacon, the snippet built an event for /e or /err. Carries wouldSend: true, and suppressedReason: 'preview' when preview mode stopped it leaving the browser. This records the attempt, not delivery.
  • server_verdict, preview mode only. Preview posts the would be payload to /e/validate, which stores nothing, and records whether the server would have accepted it. See the note below.

What server_verdict actually checks

/e/validate runs the same payload parse, the same beacon schema check, and the same site lookup as the real ingest endpoint, then returns accepted: true or a reason such as invalid_json, invalid_beacon, or site_not_found. It stops there. It does not apply blocked IP rules, does not count the visitor, and does not write anything to your results. A verdict of accepted means the payload is well formed and the site resolves, not that an equivalent live event would be recorded. If the request itself fails or is blocked, the event records serverStatus: 'unreachable' instead.

Answering common questions

“Why did my variant not apply?” Look for an assignment event for that experiment. If there is none and the experiment is missing from the loaded list, it did not match: targeting, audiences, or consent. The debugger does not record a reason for that, so narrow it by hand against the experiment’s Locations and Audiences. If there is an assignment but no variant_applied, look for a variant_apply_error in the buffer. “Why did my click goal not fire?” Check registered goals in the output; the goal is listed with its selector and state. Click the target and re run debug() to see whether a beacon event appeared. “Why is my experiment not in the debugger at all?” The config did not include it. Either the experiment is not live, or you are on a page that does not match its Location, or the site does not match the snippet (wrong snippet, wrong domain). Last verified against the shipped snippet on 2026-09-01.
Last modified on August 31, 2026