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

# JavaScript API

> Use the JavaScript API to read which test group a visitor is in, and the test price, shipping rates, and offer that test group gets, on a live storefront page.

<Note>
  The JavaScript API works on every ABConvert plan. Reading a test price needs a plan that includes price tests, and reading shipping rates needs a Shopify plan with the [Carrier Service API](/experiments/shipping-test). See [Pricing](/configuration/pricing-plans) for the full matrix.
</Note>

`window.ABConvert` is a JavaScript object on every storefront page. It tells your code which test group the visitor is in, and the test price, shipping rates, and offer that visitor sees. Use it for two jobs:

* **Keep your storefront consistent with the test.** Show or hide content by test group, or show the test price, shipping rate, or offer in an element ABConvert does not change.
* **Send the visitor's test group to your other tools.** Segment by test group in a tag manager, a data platform, or a session recording tool.
* **Record a custom event.** Call `track` when a visitor completes an action you measure.

This page is for developers who write storefront JavaScript.

The JavaScript API is read-only, and it is not present on checkout pages.

Worked scripts for each job are on [JavaScript API examples](/api-reference/browser-api-examples).

## Terminology

This page uses the following terms:

* **Test ID** is the numeric ID shown in the ABConvert admin, as a string: `'49603'`. In code it is `experimentId`: method and field names say `experiment` for a test.
* **[Test group](/experiments/overview#2-test-groups-and-traffic-allocation)** is one of the groups a test splits visitors into. Each test group has an index, which identifies it, and a name, a label you can change at any time. One test group is **Control**, which sees the store unchanged.
* **Assignment** is the test group ABConvert put the visitor in. Reading it never creates one.
* **Ready** is the moment every assignment on the page is known. Read assignments only after ready; [Step 1](#step-1-wait-for-abconvert-to-be-ready) shows how to wait for it.
* **[Combined test](/experiments/combined-test)** runs several changes as one test, so the visitor holds one assignment for it.
* **[Personalization](/personalization/overview)** shows one version to an audience, with one test group.
* **Product variant** is the Shopify catalog object. Product, product variant, and collection IDs are numeric strings, as in Liquid: `'47522361606401'`, not `'gid://shopify/ProductVariant/47522361606401'`.
* **Store currency** is the currency in your Shopify store settings, for example USD. A market can sell in another currency instead: one currency for the whole market, or each country's local currency.
* **Page currency** is the currency this page shows prices in: the store currency, or the market's currency where a market sets one.

## Confirm ABConvert is loaded

`window.ABConvert` needs no installation step. The ABConvert [app embed](/configuration/settings#connect-method-1-theme-embed-app-recommended) adds it to every storefront page except checkout. If you [installed ABConvert manually](/configuration/settings#connect-method-2-manual-installation-for-edge-cases), the snippet in `theme.liquid` does the same.

To confirm `window.ABConvert` exists, run the following in your storefront's browser console after the page has loaded:

```js theme={null}
typeof window.ABConvert;
// 'object'
```

If the result is `'undefined'`, the ABConvert app embed is off in your theme, or your store runs an older version of the ABConvert storefront scripts. Turn on the [app embed](/configuration/settings#connect-method-1-theme-embed-app-recommended); if it is already on, contact support.

## Quick start

### Step 1: Wait for ABConvert to be ready

Add this to your theme:

```js theme={null}
window.ABConvertQueue = window.ABConvertQueue || [];
window.ABConvertQueue.push(function (ABConvert) {
  console.log(ABConvert.getAssignments());
});
```

`window.ABConvertQueue` is an array of callbacks. ABConvert runs each one when it is ready, which means every assignment is known and every method works. This works whatever order the scripts load in:

* The first line creates the array if ABConvert has not yet.
* Your callback receives the `ABConvert` object as its argument.
* Callbacks run in the order you push them. A callback pushed after ready runs at once.

ABConvert is ready after the page has parsed, so your callback can find any element the theme rendered. The visitor may already see that element before your callback changes it. To avoid the flash, hide the element in your theme's CSS and show it from the callback, once it holds the right content.

A callback that throws, or an `async` callback that rejects, does not stop the callbacks after it. ABConvert logs the error to the browser console with an `[ABConvert]` prefix.

ABConvert also fires `abconvert:ready` on `window` at the same moment, once per page load. A listener added after it fired never runs, so prefer the queue. See [Events](/api-reference/browser-api-reference#events).

### Step 2: Read the visitor's assignments

`getAssignments()` returns one `Assignment` per test the visitor is in. On a page with a running test, the snippet above logs something like:

```js theme={null}
[
  {
    experimentId: '49603',
    experimentName: 'Price test - Special ski wax',
    type: 'price',
    status: 'active',
    testGroup: { index: 1, name: 'Variant A - $62.70', control: false, split: 50 },
    reason: 'random_split',
  },
]
```

`testGroup` is the test group this visitor is in. Most scripts branch on it. `reason` says how the visitor got into that test group; see [Assignment](/api-reference/browser-api-reference#assignment).

## Assignments

These methods tell you which test group the visitor landed in, per test. They never describe a test the visitor is not in.

### Read every assignment

`getAssignments()` returns an array with one `Assignment` per test the visitor is in. The array omits tests that excluded the visitor by [traffic allocation, audience targeting, or targeting rules](/targeting/overview), and is empty when the visitor is in no test.

```js theme={null}
const assignments = ABConvert.getAssignments();
// [{ experimentId, experimentName, type, status, testGroup, reason }, ...]
```

### Read one assignment by test ID

`getAssignment(experimentId)` returns one `Assignment`, or `null` when the visitor is not in that test. Find the ID in the ABConvert admin or in the REST API's [List tests](/api-reference/experiments/list-tests) response.

```js theme={null}
const assignment = ABConvert.getAssignment('49603');
// { experimentId, experimentName, type, status, testGroup, reason } or null
```

### Show content to visitors outside Control

Check for `null` first, then branch on `testGroup.control`. A visitor who is not in the test gets `null`, so the banner stays hidden:

```js theme={null}
window.ABConvertQueue = window.ABConvertQueue || [];
window.ABConvertQueue.push(function (ABConvert) {
  const assignment = ABConvert.getAssignment('49603');
  if (assignment && !assignment.testGroup.control) {
    document.querySelector('.free-gift-banner').hidden = false;
  }
});
```

To send assignments to a tag manager, a data platform, or a session recording tool, see [JavaScript API examples](/api-reference/browser-api-examples#send-assignments-to-another-tool).

## Prices

A [price test](/experiments/price-test) changes product variant prices per test group. ABConvert rewrites the price elements your theme renders on product and collection pages. Use the price methods for a price ABConvert does not change: a custom promo block, a bundle builder, a quick-view card.

The price methods need a running price test. They return the test price for the visitor's test group.

A test price is set in your store currency. A [multi-market price test](/experiments/price-test#set-up-a-price-test) can also set a price per market, in the currency that market sells in. A visitor gets their market's price when you set one, and the store-currency price otherwise.

A market price is fixed when you set it. If your store later shows a different price in that market, for example after an exchange rate change, ABConvert stops changing the price there: the visitor sees and pays your store's price. The methods still return the price you set.

They return `null` in the following cases, and `null` always means "leave the theme's price alone":

* No price test covers the product or product variant.
* The visitor is in no price test.
* The test does not run in the visitor's market: the market is outside the test, or it sells in another currency and you set no test price for it. The visitor sees your store's normal price, and still holds an assignment.

A [personalization](/personalization/overview) that changes prices reports its price the same way.

### Read the visitor's price for a product variant

`getPriceByVariantId(variantId, options?)` returns the price of one product variant for this visitor, in the market the page rendered for. The result names that market's country in `country` and its currency in `currency`.

```js theme={null}
const price = ABConvert.getPriceByVariantId('47522361606401');
// {
//   experimentId: '49603',
//   testGroup: { index: 1, name: 'Variant A - $62.70', control: false, split: 50 },
//   amount: 62.7, compareAtAmount: null, currency: 'USD', country: 'US',
//   productId: '6654464491584', variantId: '47522361606401',
// }
```

`options` is optional: `country` reads another country's price, and `aggregate` applies to `getPriceByProductId` only. Both are described below.

`amount` is a number, so you can do math on it. `formatPrice(amount, currency)` formats it for the visitor's locale:

```js theme={null}
ABConvert.formatPrice(price.amount, price.currency);   // '$62.70'
```

### Read the price for another country

Pass `country` to read the price a visitor from that country gets. The same `null` rules apply.

```js theme={null}
ABConvert.getPriceByVariantId('47522361606401', { country: 'CA' });   // Canada's price, visitor's test group
```

### Read a product's price for a collection card

`getPriceByProductId(productId, options?)` returns one price for a product, in the same market and with the same `country` option as `getPriceByVariantId`. `aggregate` picks which product variant's price:

| `aggregate`       | Returns                                                                                                 |
| ----------------- | ------------------------------------------------------------------------------------------------------- |
| `'min'` (default) | The lowest price among the product variants the test prices for this visitor. Fits "From \$X" displays. |
| `'max'`           | The highest price among those same product variants.                                                    |
| `'first'`         | The product's first product variant, or the first one the test prices for this visitor.                 |

```js theme={null}
ABConvert.getPriceByProductId('6654464491584');                            // lowest product variant price
ABConvert.getPriceByProductId('6654464491584', { aggregate: 'max' });      // highest
ABConvert.getPriceByProductId('6654464491584', { aggregate: 'first' });    // the first product variant
```

To rewrite your own price elements and keep them right as the page changes, see [Render a custom price element](/api-reference/browser-api-examples#render-a-custom-price-element).

## Shipping

A [shipping test](/experiments/shipping-test) changes the shipping rates visitors in a test group see at checkout. Use the shipping methods to show those rates before checkout, for example in a "spend \$X more for free shipping" bar.

The shipping methods need a shipping test running. They return `null` when the visitor is in no shipping test. A test group with no rates gets `[]` from `getShippingRates()` and `null` from `getFreeShippingThreshold()`.

Shopify picks the [shipping zone](/experiments/shipping-test#set-up-a-shipping-test) at checkout from the shipping address, so the browser does not know the visitor's zone. The methods return rates for every zone, each tagged with its `zone`, and the `zone` option narrows them to one.

Every rate carries the currency you set it in, which can differ from the page currency, and ABConvert does not convert it. Before you use an amount, compare `currency` with the cart's currency, as the [free shipping progress bar example](/api-reference/browser-api-examples#render-a-free-shipping-progress-bar) does.

### Read the visitor's shipping rates

`getShippingRates(options?)` returns the rates the visitor's test group offers, cheapest first within each zone and currency. One zone can hold rates in more than one currency. Pass `zone` for one zone; the method returns `[]` for a zone the test does not have:

```js theme={null}
ABConvert.getShippingRates({ zone: 'United States' });
// [
//   { experimentId: '50112', testGroup: { index: 1, … }, zone: 'United States',
//     name: 'Free shipping', amount: 0, currency: 'USD',
//     condition: { type: 'price', minimum: 75, maximum: null, unit: null } },
//   { …, zone: 'United States', name: 'Standard', amount: 5.99, currency: 'USD', condition: null },
// ]
```

### Read the free shipping threshold

`getFreeShippingThreshold(options?)` returns the lowest order subtotal at which the visitor's test group gets a free rate in a zone. Pass `zone` when the visitor's shipping tests cover more than one zone.

The method returns `null` when:

* `zone` is missing and the tests cover more than one zone.
* The zone has no free rate.
* The zone's only free rates depend on weight. A weight condition has no subtotal to report.
* The zone's free rates use more than one currency. No single lowest amount exists.

```js theme={null}
ABConvert.getFreeShippingThreshold({ zone: 'United States' });
// { amount: 75, currency: 'USD', zone: 'United States', experimentId: '50112', testGroup: { index: 1, … } }
```

To render the bar and keep it current as the cart changes, see [Render a free shipping progress bar](/api-reference/browser-api-examples#render-a-free-shipping-progress-bar).

## Offers

An [offer test](/experiments/offer-test) gives a test group a discount: an [amount off products or the order, a shipping discount, or a volume or threshold discount](/experiments/offer-test#offer-types). Use the offer method to show the visitor's offer in your own banner or table. The offer method exists only while an offer test is running or in preview.

### Read the visitor's offers

`getOffers()` returns one `Offer` per offer test the visitor is in. A test group with no offer contributes no `Offer`, so an empty array means "show nothing."

```js theme={null}
ABConvert.getOffers();
// [
//   {
//     experimentId: '50340',
//     testGroup: { index: 1, name: 'Buy 2 save 10%', … },
//     title: 'Buy 2 save 10%',
//     discounts: [
//       { type: 'volume_discount', scope: { type: 'all_products' },
//         tiers: [{ threshold: 2, value: { unit: 'percentage', value: 10 } },
//                 { threshold: 4, value: { unit: 'percentage', value: 20 } }] },
//     ],
//   },
// ]
```

`discounts` lists the offer's discounts; see [Offer](/api-reference/browser-api-reference#offer). The JavaScript API does not compute tier progress: to show "add 1 more to save 20%", compare `tiers` with the cart.

To show the offer and the visitor's progress toward the next tier, see [Render an offer banner](/api-reference/browser-api-examples#render-an-offer-banner).

## Track custom JS events

A [custom JS event](/analytics/custom-events#custom-js-event) is an action you record from your own code, for example a completed product builder. `track(name, payload?)` records one. `name` is the event's slug from the [custom event library](/analytics/custom-events#understand-the-custom-event-library); `payload.value` is an optional value.

```js theme={null}
window.ABConvertQueue = window.ABConvertQueue || [];
window.ABConvertQueue.push(function (ABConvert) {
  ABConvert.track('product_builder_completed');
});
```

ABConvert records an event at most once per page load. It ignores an unknown slug, and a call on a page outside the event's [page scope](/analytics/custom-events#set-the-page-scope).

## Common mistakes

The following patterns break storefront scripts:

* **Writing a price when the method returns `null`.** `null` means the test does not price this product variant for this visitor. The fix: leave the theme's price alone.
* **Writing a value that is already correct.** ABConvert watches the page for changes, so every write you make restarts its scan. If you watch the page with your own observer, an unconditional write fires it again, loops, and freezes the tab. The fix: compare before you write, as every example does.
* **Polling for ABConvert with a timer.** The fix: push your callback onto `window.ABConvertQueue`; it runs as soon as ABConvert is ready.
* **Using `testGroup.name` as an identifier.** The name is a display label that can change while the test runs. The fix: send `testGroup.index` to other tools and branch on `testGroup.control` in your code.
* **Passing a product ID to `getPriceByVariantId`.** It takes the product variant ID and returns `null` for anything else. The fix: use `getPriceByProductId` for a product-level price.

## Force a test group

To see one test group yourself, force it from the console. Do not force in production code. A forced assignment is left out of results and carries `reason: 'url_force_assign'`.

`forceTestGroup(experimentId, index)` puts you in a test group from the next page load, and `clearForcedTestGroup(experimentId)` undoes it. The force stays with the browser tab until you clear it or close the tab. An `index` past the last test group shows Control:

```js theme={null}
ABConvert.forceTestGroup('49603', 1);     // Variant A. Reload to see it.
ABConvert.clearForcedTestGroup('49603');  // Undo. Reload.
```

To force a test group with a link instead, see [See one test group with a link](/experiments/lifecycle#see-one-test-group-with-a-link).

## Reference

Every global, method, event, and object is on [JavaScript API reference](/api-reference/browser-api-reference).

## FAQ

<Accordion title="window.ABConvert is undefined">
  In the browser console, run `typeof window.ABConvert` after the page has loaded. If the console shows `'object'` but your script sees `undefined`, your script ran before ABConvert loaded: push your callback onto `window.ABConvertQueue`. If the console also shows `'undefined'`, either the app embed is off or your store runs an older version of the ABConvert storefront scripts; see [Confirm ABConvert is loaded](#confirm-abconvert-is-loaded).
</Accordion>

<Accordion title="getPriceByVariantId() returns null">
  First check that you passed the product variant ID, not the product ID. Otherwise `null` means no test price applies to this visitor; see [Prices](#prices). Leave the theme's price alone.
</Accordion>

<Accordion title="getFreeShippingThreshold() returns null even though the test is running">
  Call `getShippingRates()` to list every zone. If more than one zone comes back, pass `zone`. If the zone has no `amount: 0` rate, or its only `amount: 0` rate has a `weight` condition, no threshold exists. If the zone's free rates are in more than one currency, no single threshold exists: read the rates for the cart's currency from `getShippingRates()` instead.
</Accordion>

<Accordion title="My callback throws but the page keeps working">
  ABConvert catches an error thrown or rejected inside a `window.ABConvertQueue` callback, so the callbacks after yours still run. Look for a console line that starts with `[ABConvert]`: it carries your error and its stack.
</Accordion>

<Accordion title="Can I track a conversion or set visitor attributes from the browser?">
  No. The JavaScript API is read-only. ABConvert attributes conversions and revenue from Shopify orders.
</Accordion>
