Storefront JavaScript API

Storefront JavaScript API

Overview

Theme JavaScript or another Shopify app on the same page can read the live calculator and set option values. There is no API key — any script on the storefront can use this surface, same as other custom JavaScript (protect against XSS).

This page covers the events to listen for, how to read values (including quantity), and how to set a value. Use it from a theme script, another app’s script, or the browser console. If you are writing a Global script inside Calculator Editor → Placement & Styling → JavaScript, Appify CPO runs that script for you — see Custom JavaScript.

Listen for events

Attach listeners on document. The calculator dispatches these custom events:

Event When it fires Typical use
calc:ready The calculator has loaded Start listening, or call window.CalcAI.setValue
calc:change After every price/value update Read current option values, quantity, and totals
calc:customJsReady This calculator’s editor Custom JS has finished startup Theme or third-party scripts that depend on those scripts

How to listen — pass the event name and a function. The payload is on ev.detail:

document.addEventListener("calc:ready", (ev) => {
  const { instanceId, calculatorId } = ev.detail || {};
  console.log("Calculator ready", calculatorId, instanceId);
});

document.addEventListener("calc:change", (ev) => {
  const { values, totals, external } = ev.detail || {};
  if (external) return; // skip changes your own script just made
  console.log("Values", values);
  console.log("Total", totals && totals.total);
});

Add these listeners once (for example in a theme JS file or a Custom HTML / Custom Liquid section). You do not need to query a calculator element first.

If your theme script must wait until editor Custom JS has finished, listen for calc:customJsReady instead of calc:ready, or call window.CalcAI.isCustomJsReady(calculatorId). Do not listen for calc:customJsReady inside a Global script in the editor — that script is already running at that moment.

What calc:change includes

Field What it is
values Current option values, keyed by each option’s Key (not the customer-facing label). Example: { WIDTH: 48, QUANTITY: 25, COLOR: "red" }
totals Pricing snapshot. Common fields: total, subtotal, discountAmount, orderFees
spec Calculator spec, including spec.options (id, key, type, label)
summary Payload used by price-summary blocks, or null
reason Why it fired — "recompute", "draw", or "external-set"
external true when the change came from CalcAI.setValue / calcai:setValue
instanceId Instance id (often null on a single-calculator page)
calculatorId Calculator id
productId Linked product id when present

Find each option’s Key in Calculator Editor → Options & Pricing (the Key field on the option).

Read the quantity

Quantity is a normal option. Read it from values using that option’s key. New quantity options usually use the key QUANTITY.

document.addEventListener("calc:change", (ev) => {
  const values = ev.detail?.values || {};
  const quantity = Number(values.QUANTITY);
  if (!Number.isFinite(quantity)) return;
  console.log("Quantity", quantity);
});

If you renamed the key, look up the quantity option by type, then read that key:

document.addEventListener("calc:change", (ev) => {
  const detail = ev.detail || {};
  const options = detail.spec?.options || [];
  const qtyOpt = options.find((o) => String(o.type || "").toLowerCase() === "quantity");
  if (!qtyOpt) return;
  const quantity = Number(detail.values?.[qtyOpt.key]);
  console.log("Quantity", quantity, "key", qtyOpt.key);
});

There is no separate getValue or getQuantity helper. Keep the last calc:change payload in a variable if another function needs the current quantity later.

Set option values

Wait until the calculator is ready, then call window.CalcAI.setValue. optionId can be the option Key or the option id.

document.addEventListener("calc:ready", () => {
  const out = window.CalcAI.setValue({
    optionId: "WIDTH",
    value: "48",
  });
  if (!out?.ok) console.warn(out?.code, out?.error);
});

You can also check typeof window.CalcAI?.setValue === "function" before calling. The function returns { ok: true } or { ok: false, error, code }.

The same update can be sent as an event. That path has no return value — use setValue when you need to handle errors:

document.dispatchEvent(
  new CustomEvent("calcai:setValue", {
    bubbles: false,
    detail: { optionId: "WIDTH", value: "48" },
  })
);

Value shapes

Option types value to send
Dropdown, radio, button, swatch, image swatch String matching a choice value / key / id
Switch Boolean, or "true" / "false"
Number, slider, quantity Number (or numeric string)
Text, textarea, email, color, and similar String
Checkbox group / multi swatch Array of strings
Dimensions Object (or JSON object string)
File upload, modal Not supported (UNSUPPORTED_TYPE)

Details

Several calculators on one page

Pass instanceId and/or calculatorId from calc:ready (or from calc:change) so the call targets the right calculator. On a page with only one calculator, those fields are often null and the API still resolves the only instance.

document.addEventListener("calc:ready", (ev) => {
  const { instanceId, calculatorId } = ev.detail || {};
  window.CalcAI.setValue({
    optionId: "QUANTITY",
    value: 10,
    instanceId,
    calculatorId,
  });
});

If several calculators are present and you omit the ids, setValue returns AMBIGUOUS_INSTANCE.

Avoid feedback loops

When your script sets a value, the next calc:change has detail.external === true and reason === "external-set". Ignore those events if you are echoing state to analytics, a chat widget, or another setter.

Limitations

  • File and modal options cannot be set through this API.
  • Hidden options (visibility rules) cannot be set (OPTION_HIDDEN).
  • If the storefront loads a fallback runtime without the full calculator renderer, setValue is unavailable (NO_CALC_INSTANCE). Standard theme blocks use the full renderer.

Error codes

Returned on { ok: false } from window.CalcAI.setValue:

Code Meaning
NO_CALC_INSTANCE No registered calculator (not loaded yet, or fallback runtime)
AMBIGUOUS_INSTANCE Several calculators on the page and ids were missing
INVALID_PAYLOAD Missing or invalid optionId / detail
UNKNOWN_OPTION optionId did not match an option id or key
OPTION_HIDDEN Option is hidden by visibility rules
INVALID_CHOICE Value is not an allowed choice
UNSUPPORTED_TYPE Type cannot be set (file, modal)
COERCION_FAILED Value could not be converted to the expected type

Examples

Bulk stickers — log quantity and line total as the shopper changes the calculator

document.addEventListener("calc:change", (ev) => {
  const detail = ev.detail || {};
  if (detail.external) return;
  const quantity = Number(detail.values?.QUANTITY);
  const total = detail.totals?.total;
  console.log("Stickers", quantity, "total", total);
});

Custom blinds — set width from your theme after the calculator is ready

document.addEventListener("calc:ready", () => {
  const out = window.CalcAI?.setValue({
    optionId: "WIDTH",
    value: 36,
  });
  if (!out?.ok) console.warn(out?.code, out?.error);
});

Engraved jewelry — set quantity to 2, then ignore the echo

document.addEventListener("calc:change", (ev) => {
  if (ev.detail?.external) return;
  const qty = Number(ev.detail?.values?.QUANTITY);
  console.log("Shopper quantity", qty);
});

document.addEventListener("calc:ready", () => {
  window.CalcAI?.setValue({ optionId: "QUANTITY", value: 2 });
});