Developer API
Optionfier exposes a lightweight event system and a global JavaScript API on the storefront. Theme developers and third-party scripts use these to react to option changes, read current selections, and integrate Optionfier data into custom UI. This page assumes you're already familiar with the merchant-facing concepts on the main docs page, in particular Appears As: Bundle Items or Text on Order.
Events
Optionfier dispatches four custom events on its mount element with bubbles: true, so you can listen at any level of the document. The mount is normally #optionfier, but it's #optionfier-byob instead when a product uses the dedicated Build-a-Box block. Listening on document sidesteps the difference, since the events bubble up regardless of which element they started on:
document.addEventListener('optionfier:change', (e) => {
console.log(e.detail);
});optionfier:init
Fires immediately when the Optionfier script loads, before any API calls or rendering. Use this to show loading indicators or prepare your UI.
Payload:
{
productId: "gid://shopify/Product/123456", // Shopify product GID
shop: "your-store.myshopify.com" // Store domain
}optionfier:loaded
Fires once after the option data has loaded from the API and the option fields have rendered on the page. Use this to hide loading indicators or initialize UI that depends on Optionfier being ready.
Payload: Full state snapshot (see State Snapshot Shape below).
optionfier:change
Fires each time a customer interacts with an option field: selecting a dropdown value, typing in a text field, picking a date, and so on. This event fires only on direct user interactions, not on system-driven changes like visibility cascading or sold-out state updates.
Payload:
{
// What changed
optionId: "abc123",
label: "Engraving Text",
value: "Happy Birthday", // New value (null if cleared)
previousValue: "Hello World", // Previous value (null if was empty)
// Full state snapshot (reflects state AFTER the change)
groupId: "group_1",
groupName: "Custom Engraving Options",
productId: "gid://shopify/Product/123456",
isSoldOut: false,
options: [ /* ... */ ]
}optionfier:submit
Fires when the product form is submitted (add to cart). This event fires during the capture phase, before the theme's own bubble-phase submit handlers run. Use it for analytics tracking or last-moment validation.
Payload: Full state snapshot (see State Snapshot Shape below).
window.Optionfier.getState()
Call this function at any time after optionfier:loaded has fired to get a snapshot of the current option state.
const state = window.Optionfier?.getState();
if (state) {
console.log('Product:', state.productId);
for (const os of state.options) {
if (os.isVisible && os.value) {
console.log(`${os.label}: ${os.value}`);
}
}
}Returns undefined if called before the options have loaded (the function itself doesn't exist yet, since window.Optionfier isn't installed until load) or if Optionfier isn't present on the page. Always use optional chaining (?.) to guard against it.
State Snapshot Shape
The optionfier:loaded, optionfier:change, and optionfier:submit events all include a state snapshot, and window.Optionfier.getState() returns the same shape.
{
groupId: "group_1", // Internal group ID
groupName: "Custom Engraving Options", // Group display name
productId: "gid://shopify/Product/123456", // Shopify product GID
isSoldOut: false, // Whether the entire group is sold out
options: [
{
id: "opt_1",
type: "select", // Option type (see table below)
label: "Frame Material",
value: "Oak", // Current selection (null if empty)
price: "10.00", // Price modifier if set (null otherwise)
isSoldOut: false,
isVisible: true,
isRequired: true,
appearsAs: "bundle" // "bundle", "text", or "display", see below
},
{
id: "opt_2",
type: "text",
label: "Engraving Text",
value: "Happy Birthday",
price: null,
isSoldOut: false,
isVisible: true,
isRequired: false,
appearsAs: "text"
}
]
}Reading an option's Appears as mode
appearsAs mirrors the merchant's Appears as setting. It's per option, so one product can mix bundle and text options.
"bundle": a real product priced into the cart, with Shopify tracking its inventory."text": text on the order, no price change (though a linked variant's stock may still track in the background)."display": structural content with no value. Skip these when reading customer input.
Option Type Values
The type field on each option in the snapshot maps to the following values:
| Option Type | type value |
|---|---|
| Selectable (dropdown, radio, buttons, product grid) | "select" |
| Checkbox | "checkbox" |
| Text input | "text" |
| Number input | "number" |
| Date picker | "date" |
| Time picker | "time" |
| File upload | "file" |
| Color swatches | "color-swatch" |
| Image swatches | "image-swatch" |
| Dynamic color picker | "color-picker" |
| Layout and content (heading, paragraph, divider, spacer) | "unknown" |
An entry with type: "unknown" always has appearsAs: "display" and no value. Check appearsAs rather than type if you're specifically looking for these.
Example: Live Preview Based on Selections
document.addEventListener('optionfier:change', (e) => {
const { options } = e.detail;
const color = options.find(os => os.label === 'Color')?.value;
const text = options.find(os => os.label === 'Engraving Text')?.value;
if (color) {
document.querySelector('.preview-swatch').style.backgroundColor = color;
}
if (text) {
document.querySelector('.preview-text').textContent = text;
}
});Example: Waiting for Optionfier to Load
document.addEventListener('optionfier:loaded', (e) => {
const { options } = e.detail;
const requiredCount = options.filter(os => os.isRequired).length;
console.log(`${requiredCount} required options loaded`);
// Safe to call getState() from here on
const state = window.Optionfier.getState();
});Notes
If your event listener throws, Optionfier keeps working normally: all event dispatching is wrapped in error handling. Events are silently discarded if no Optionfier mount element is present on the page.