The on-page JavaScript contract of the Print Options configurator: read state, write selections and file facts, subscribe to changes, and gate add-to-cart.
Scripts running on the product page — a design tool, an upload service, your
own code — can talk to the configurator directly. Read what the customer has
chosen, write into it, react to changes, and block add-to-cart until your own
step is complete.Everything here is draft state. The price that gets charged is always
recomputed and signed on our servers at add-to-cart, so this API is open by
design: a script can only produce a correctly priced cart line, or a refusal.
// The four sizes this merchant sells, whatever they named the fieldconst sizes = po.field("size").options;
Both state and schema hand back copies, so mutating what you read never
affects the configurator.state.price carries total, quantity, unitPrice, the summary lines,
and unavailable. While unavailable is true the job can’t be added to the
cart, and unavailableReason says why: "combination" when the merchant has
ruled the combination out, "formula" when a
formula price rule has no price for
this size or quantity.
// Set one answer, as if the customer chose itpo.setSelection("paper", "matte", { source: "my-app" });// Several at once — one recalculation, one change eventpo.setSelections({ size: "a4", qty: 250 }, { source: "my-app" });// Attach file facts — this is how an upload service drives per-page pricingpo.setFile({ fileId: "job-77", source: "my-app", pages: 12 }, { source: "my-app" });
Writes go through exactly the same validation and pricing path as a customer’s
click. Unknown fields or invalid values are ignored with a console warning —
never an exception, so a partner script cannot break the product page.
A quantity is stored as the number it is priced at: snapped to the field’s
step and kept within its minimum and maximum. Writing 333 to a field sold in
steps of 25 stores 325, and state.selections and the next change event
report 325, so read the value back rather than assuming the one you wrote.A quantity per option
field (a size run) answers with a map of counts, keyed by the field’s option
ids. Write the whole map; a 0 clears an option, and an unknown option id
refuses the write:
po.setSelection("sizes", { m: 5, l: 3, "3xl": 2 }, { source: "my-app" });po.state.quantity; // 10, the total
po.field("quantity") returns the quantity field currently in play: the
visible one when several take turns,
and a quantity per option field when that is what the product uses.
po.addEventListener("change", (event) => { const { source, changed, price, selections } = event.detail; if (source === "my-app") return; // ignore your own writes if (changed.includes("selections.pages")) { updateMyPreview(selections.pages); }});
Every event carries the source tag of the write that caused it and a
changed list of what moved. Tag your writes and ignore your own echoes —
that is all loop prevention takes. Writing a value that is already set emits
nothing at all.
Named holds let several integrations gate independently without fighting
each other. The button re-enables only when every hold is released.
po.addHold("my-app", { message: "Finish your design to continue" });// ...when your step completes:po.releaseHold("my-app");
While a hold is active the Add to cart button is disabled and your message
shows beside it. Validation, by contrast, never disables the button — clicking
an incomplete form scrolls to what is missing, which is how customers discover
it.
The option set is loaded and initial state computed
Full state
change
Any state transition — selection, file, quantity, hold, validity
Full state, plus source and changed
submit
The customer added a valid configuration to the cart
Selections, quantity, price, file, display pairs
invalid
Add-to-cart was attempted while invalid or held
The issues, or the holds
ready and change carry a plain object in event.detail. The older
submit, invalid and unconfigured events pre-date this API and carry
an array — read event.detail[0] for those.
The live demo exposes the element as
window.po. Open your browser console there and try the calls above
against a real configurator.