Guided bundles: JavaScript events and methods
BYOB guided bundles provide JavaScript events and a getState() method for custom storefront integrations.
You can use them to track customer actions, display a custom bundle summary, update progress labels, or build custom navigation controls such as a sticky footer.
These events apply to the BYOB Guided Bundle configurator. For full-page bundles, see Full-page bundle callbacks and methods.
Where to put your code
Open the Shopify theme editor and select the product template containing your BYOB Guided Bundle block.
Add a Custom Liquid section with your JavaScript inside <script> tags. Place it before the guided bundle block so your listeners can capture initialization events.
You can also place the code in your theme’s JavaScript files. Keep custom code in your theme rather than editing BYOB’s extension assets.
Available events
Listen for events using document.addEventListener(). The payload is available through event.detail.
Every event includes event.detail.state, which contains the current bundle snapshot.
| Event | When it fires | Additional payload |
|---|---|---|
byob:configurator:ready |
The configurator is initialized and its state getter is available | None |
byob:configurator:step-changed |
The displayed step changes | previousStep |
byob:configurator:selection-changed |
Selected items or their quantities change | changes, reason |
byob:configurator:state-changed |
Bundle state changes, including selections, pricing, step, or button availability | None |
byob:configurator:review-shown |
The customer enters the review screen | None |
byob:configurator:added-to-cart |
A bundle is successfully added to cart, before the cart drawer updates or the page redirects | submittedItems, cartResponse |
For example:
<script>
document.addEventListener('byob:configurator:step-changed', function(event) {
const step = event.detail.state.currentStep;
console.log('Step ID:', step.id);
console.log('Step index:', step.index);
console.log('Step title:', step.title);
console.log('Total steps:', step.totalSteps);
console.log('Review screen:', step.isReview);
});
</script>
Step indexes start at 0. totalSteps excludes the review screen. Use isReview to identify review rather than displaying it as another selection step.
Reading the current state
Use getState() when your script needs the current state without waiting for the next event:
const container = document.querySelector('.byob-bundle-configurator-app');
if (container && container.byobConfigurator) {
const state = container.byobConfigurator.getState();
console.log(state);
}
The getter becomes available when the configurator is ready. It returns a copy of the latest completed state and does not change the bundle.
For scripts that may load before or after initialization, use both event listeners and the getter:
<script>
(function() {
function updateSummary(container, state) {
let totalText = 'Calculating…';
if (state.pricingStatus === 'ready') {
totalText = new Intl.NumberFormat(undefined, {
style: 'currency',
currency: state.currencyCode
}).format(state.totalCents / 100);
} else if (state.pricingStatus === 'error') {
totalText = 'Price unavailable';
}
// Replace these logs with updates to your custom UI.
console.log('Bundle:', state.bundleId);
console.log('Total:', totalText);
console.log('Item count:', state.itemCount);
console.log('Next enabled:', state.canGoNext);
}
function handleState(event) {
updateSummary(event.target, event.detail.state);
}
document.addEventListener('byob:configurator:ready', handleState);
document.addEventListener('byob:configurator:state-changed', handleState);
document.querySelectorAll('.byob-bundle-configurator-app')
.forEach(function(container) {
if (container.byobConfigurator) {
updateSummary(container, container.byobConfigurator.getState());
}
});
})();
</script>
After triggering a button, wait for state-changed to read the result. Selection rules and UI updates are processed before the new state is published.
State fields
These fields are available in getState() and event.detail.state.
| Field | Description |
|---|---|
apiVersion |
Public API version. Currently 1 |
instanceId |
Identifier for this mounted configurator |
bundleId |
Bundle ID as a string, or null |
isDesignMode |
Whether the configurator is running in Shopify’s theme editor |
currentStep |
Current step’s id, index, title, totalSteps, and isReview |
selectedItems |
List of selected item lines |
itemCount |
Total selected variant quantity |
subtotalCents |
Total before the BYOB bundle discount |
totalCents |
Total after the BYOB bundle discount |
currencyCode |
Currency used for totals, such as USD |
pricingStatus |
loading, ready, or error |
currentStepRequirementMet |
Whether the current selection step’s requirement is met; null on review |
allStepsRequirementsMet |
Whether all selection steps meet their requirements |
canGoBack |
Whether the current native Back button is available and enabled |
canGoNext |
Whether the current native Next button is available and enabled |
canAddToCart |
Whether the current native Add to Cart button is available and enabled |
isAddingToCart |
Whether a cart request is in progress |
Prices and quantities
Divide money values by 100 before formatting them in the supplied currency. For example, totalCents: 2500 represents an amount of 25.While prices load, totals may be null and pricingStatus will be loading. A subsequent state-changed event supplies the resolved total. A failed calculation uses pricingStatus: 'error'.
Zero is a valid total. null does not mean zero.
Totals match the configurator’s review estimate. They exclude shipping, taxes, and additional cart or checkout adjustments.
itemCount counts quantities, not distinct products. Selecting three units of one variant gives an item count of three. Virtual choices do not contribute to this count.
Button availability mirrors BYOB’s native controls. It does not guarantee cart submission will succeed: the app still checks access, draft status, and cart errors.
Selected item fields
Each entry in selectedItems contains:
| Field | Description |
|---|---|
stepId |
Step containing the selection |
optionId |
Selected option’s ID |
type |
Configured option type |
title |
Item’s display title |
productId |
Shopify product ID |
variantId |
Shopify variant ID |
handle |
Product handle |
quantity |
Selected quantity |
unitPriceCents |
Unit price before the bundle discount |
currencyCode |
Currency for the unit price |
Shopify IDs are strings. Product metadata and prices may be null while loading or when unavailable.The same variant selected in different steps appears as separate lines. Identify a line using stepId, optionId, and variantId together.
Virtual choices have no Shopify product or variant where those fields do not apply.
Tracking selection changes
The selection-changed event includes a changes array. Each change contains:
| Field | Description |
|---|---|
item |
The affected item, using the selected-item fields above |
action |
added or removed |
previousQuantity |
Quantity before the change |
quantity |
New quantity; zero for complete removal |
quantityDelta |
New quantity minus previous quantity |
For example:
<script>
document.addEventListener('byob:configurator:selection-changed', function(event) {
console.log('Change reason:', event.detail.reason);
event.detail.changes.forEach(function(change) {
console.log('Variant:', change.item.variantId);
console.log('Action:', change.action);
console.log('New quantity:', change.quantity);
console.log('Quantity delta:', change.quantityDelta);
});
});
</script>
Increasing quantity from one to three reports added with quantityDelta: 2. Decreasing it from three to one reports removed with quantityDelta: -2.
Replacing a variant reports removal of the previous variant and addition of the new one. For complete removal, item retains the previous item details; use change.quantity and change.quantityDelta to interpret the change.
The event also provides a reason:
| Reason | Meaning |
|---|---|
user |
A customer selection action |
back |
Backward navigation cleared later selections |
reset |
Reset cleared selections |
rule |
Configurator rules changed selections |
mixed |
Multiple origins contributed to the same update |
Going backward can therefore emit both step-changed and selection-changed.Rejected selections and unchanged quantities do not emit selection changes. Price updates alone emit state-changed, without reporting another product addition.
Running code after adding to cart
<script>
document.addEventListener('byob:configurator:added-to-cart', function(event) {
console.log('Submitted items:', event.detail.submittedItems);
console.log('Cart response:', event.detail.cartResponse);
// Add your custom code here.
});
</script>
submittedItems records the selections submitted with that request. cartResponse is Shopify’s successful add response, not the complete cart.
Failed requests do not emit this event. It confirms an addition to cart, not a completed purchase.
isAddingToCart may still be true during the success event. A later state update reflects completion of the submission handler.
Using custom navigation buttons
Custom controls can trigger BYOB’s existing buttons so validation remains in the app.
Use these selectors inside the configurator container:
| Action | Selector |
|---|---|
| Back | [data-byob-action="back"] |
| Next | [data-byob-action="next"] |
| Add to Cart | [data-byob-action="add-to-cart"] |
function triggerBYOBAction(container, action) {
const button = container.querySelector(
'[data-byob-action="' + action + '"]'
);
if (button && !button.disabled) {
button.click();
}
}
Pass back, next, or add-to-cart as the action.
For a sticky footer, use the state’s availability fields to enable or disable your controls. Switch Next to Add to Cart when currentStep.isReview is true.
Find the native button on each click rather than saving its element permanently, because changing steps can replace it.
Important integration notes
- Events provide notifications. Returning
falseor callingpreventDefault()does not block BYOB actions. - Initialization emits
readyandstate-changed. Use these to read initial state;step-changeddescribes subsequent step changes. review-shownfires each time the customer enters review.- Several events can describe the same update. For analytics, avoid counting a selection event and its state update as separate selections.
- Events listened to on
documentcan come from any configurator instance.event.targetidentifies the source container. - For analytics, check
state.isDesignModeto exclude theme-editor interactions.