Guides

JavaScript SDK

Install the browser SDK, the full API, single-page apps and how to avoid flicker.

The SDK is a single dependency-free script, sdk.js, served by freeabtest.com. The shipped file is about 50 KB raw and about 14 KB gzipped. It exposes one global object, window.ABTest.

Install#

When you are signed in, Overview, Domains, and each experiment Setup tab show a pre-filled snippet with your public SDK key and https://www.freeabtest.com. Copy it as-is. Use the Production / Development toggle to swap keys; you do not paste a key yourself.

HTML
<script src="https://www.freeabtest.com/sdk.js" data-key="cro_pk_prod_..." async></script>

Put the same tag on the control page and every variation page. The SDK reads the key from data-key, matches a running test by the current URL, and handles bucketing, redirects and click goals automatically.

Coming later

An npm package and server SDKs are planned. Today, load the script tag as shown.

init(options)#

Call once per page before anything else. It stores the configuration and makes no network request.

OptionRequiredDescription
apiUrlYesThe base URL shown in your experiment's Setup snippet, with no trailing slash. Requests go to apiUrl + /api/assign and apiUrl + /api/track.
apiKeyYesAn SDK key (cro_pk_<env>_...). The key decides which project and environment the data goes to.
userIdNoYour own stable visitor ID, such as a logged-in user ID. If omitted, the SDK generates one and keeps it in localStorage under ab_user_id.

If apiUrl or apiKey is missing, init logs an error to the console and the SDK stays uninitialised.

Visitor IDs#

Generated IDs look like user_1728912345678_k3j9x0abc. They live in localStorage, so they survive reloads but not a different browser, device or a cleared storage. Pass userId if you want the same person to see the same variation across devices. IDs must be 1 to 200 printable ASCII characters with no spaces.

run(experimentId, callback, options)#

The main entry point. Gets the assignment, handles redirects for split URL tests, then calls your callback with the result. Returns a promise that resolves to the same object.

JavaScript
ABTest.run('exp_123', function (variation) {
  if (!variation.assigned) return;
  if (variation.variation_name === 'Variant B') {
    document.body.classList.add('variant-b');
  }
});

If the assigned variation has a redirect URL and the visitor is not already on it, run sends the browser there and does not call the callback. Pass { autoRedirect: false } as the third argument to turn that off and handle variation.variation_url yourself.

getVariation(experimentId)#

Lower level than run. Returns a promise with the assignment and never redirects. Results are cached for the life of the page, so calling it twice makes one request.

Response
{
  "experiment_id": "exp_123",
  "variation_id": "var_456",
  "variation_name": "Variant B",
  "variation_url": "https://example.com/pricing-b", // only for split URL tests
  "assigned": true
}

When the visitor is not in the experiment, assigned is false and the other fields are empty. Network errors also resolve to { assigned: false }, so your page always falls back to the control.

Note

The first time a visitor is assigned on a page, the SDK automatically sends a page_view event. That is what counts them as a visitor in the results.

trackConversion(experimentId, eventName, metadata)#

Records a conversion for the visitor's current variation. eventName is an optional label such as signup. metadata is an optional object, up to 4 KB as JSON.

JavaScript
ABTest.trackConversion('exp_123', 'purchase', { plan: 'team', value: 49 });

Assignment first

trackConversion uses the assignment cached on this page. If you have not called run or getVariation for this experiment on the same page load, it logs a warning and sends nothing. On a thank-you page, call getVariation first, then track.

Thank-you page
ABTest.getVariation('exp_123').then(function () {
  ABTest.trackConversion('exp_123', 'purchase');
});

trackEvent(experimentId, variationId, eventType, eventName, metadata)#

The raw call behind the other two. eventType must be page_view or conversion. You rarely need it directly.

runUrlTest(experimentId)#

Shorthand for run(experimentId, null, { autoRedirect: true }). Use it on the original page of a split URL test.

getUserId()#

Returns the visitor ID the SDK is using. Handy for debugging and for joining with your own analytics.

Single-page apps#

The SDK has no router integration yet. In React, Vue or similar, call run or getVariation when the relevant view mounts. Because results are cached for the page, route changes do not cause new requests, and the visitor keeps their variation.

React
useEffect(() => {
  window.ABTest.getVariation('exp_123').then((v) => {
    setHeadline(v.assigned && v.variation_name === 'Variant B' ? 'New headline' : 'Old headline');
  });
}, []);

The automatic page view is sent once per experiment per page load, not on every route change. That is fine for results, which count unique visitors.

Avoiding flicker#

Today the SDK makes one network request per experiment before it knows the variation. If the original content renders first, visitors may briefly see it change. To reduce this:

  • Load sdk.js and call run in the <head>, not at the end of the body.
  • Hide only the element under test until the callback runs, with a short timeout so the page never stays hidden.
  • Prefer changes that do not shift layout, such as text and colour.
Anti-flicker pattern
<style>.ab-hide { opacity: 0 !important; }</style>
<h1 class="ab-hide">Original headline</h1>
<script>
  var h1 = document.querySelector('h1');
  var reveal = function () { h1.classList.remove('ab-hide'); };
  setTimeout(reveal, 800); // never hide for long
  ABTest.run('exp_123', function (v) {
    if (v.assigned && v.variation_name === 'Variant B') h1.textContent = 'New headline';
    reveal();
  });
</script>

Rate limits and errors#

Requests are limited per SDK key and client IP. All SDK methods catch errors and log them to the console with an [ABTest] prefix, so a failure never breaks your page.