Before you start
- Turn on the Appstle Bundles app embed in the theme editor (App embeds). The slider cart interfaces also need the Appstle Slider Cart app embed.
- The embed adds its script at the end of the page body, so
window._ABConfigdoesn’t exist yet while scripts earlier in the page run. Run your code from a script withdefer, or in aDOMContentLoadedlistener. - The cart refresh helpers are in place by
DOMContentLoaded. - On paths the merchant lists under Bundle Excluded Paths on the Shop Settings page, the embed loads nothing and
window._ABConfigis undefined. Check for it before you call a method, for example withwindow._ABConfig?.whenConfigReady(...).
Read bundle configuration
The Bundles app embed setswindow._ABConfig. These are its documented members:
bundle_rules(array): the store’s active bundle rules that the page has, in the same shape as theappstle_bundles.bundle_rulesshop metafield. Several fields are JSON-encoded strings; see Reading bundle configuration.bundle_setting(object): the store’s Bundles settings, including the text labels the widgets show.whenConfigReady(callback): runscallbackonce the settings and the page’s rules are in place, or at once on a page that loaded with them.loadAllBundleRules(): returns a promise that resolves with every rule. It never rejects.hasBundleRulesFor(productIds): returnstruewhen the page has the rules for every product inproductIds, an array of numeric Shopify product IDs (numbers or strings, not GIDs), or when the page already has all rules.getProductDiscountedPricing(options): returns a preview of the bundle discount for one variant. See Preview a discount.
Rules and settings can arrive after the page loads
On some stores, each page starts withwindow._ABConfig.bundle_rules empty and only the labels in window._ABConfig.bundle_setting, and the embed fetches the rest right away. On other stores, the page leaves out the volume discount and Buy X Get Y rules that apply to specific products, unless the page is the product page of one of those products or one of them is in the cart. There, the embed fetches the rest only when code calls loadAllBundleRules(), so call it rather than wait for an event.
Both happen only while the app embed’s Load bundle rules only where needed setting is on, which it is by default. With it off, every page loads with every rule.
whenConfigReady(callback)runscallbackonce the settings and the page’s rules are in place: when the fetch finishes on the first kind of store, and at once on the second. If that fetch fails, it still runscallback, withbundle_rulesempty and only the labels inbundle_setting.loadAllBundleRules()fetches the rules the page doesn’t have and resolves with every rule. If they can’t load, it resolves with the rules the page has, which can be an empty list, and the next call tries again.- When the rest arrive, the embed fills
bundle_rules(and, on the first kind of store,bundle_setting) in place and dispatchesappstle-bundles:rules-loadedondocument. Don’t replace the array or the object, and read them again after the event.
whenConfigReady calls its callback at once and loadAllBundleRules() resolves at once with the same array as bundle_rules. On such a page, appstle-bundles:rules-loaded never fires.
Preview a discount
window._ABConfig.getProductDiscountedPricing(options) returns the best volume discount or Product Discount a variant gets at a quantity. It runs in the browser and makes no request.
When the tier that applies has no discount, such as a 1 item tier at the standard price,
discountValue and discountAmount are 0 and discountedPrice equals totalAmount. When no rule or tier applies, for example at a quantity below a rule’s lowest tier or for a variant no rule includes, every discount field is undefined.
- Only active volume discount and Product Discount rules that include the variant count. Buy X Get Y rules don’t.
- Rules that target collections count only on a product page, where the helper matches them against that page’s product.
- Rules limited to customer tags or to a number of uses per customer count only for a signed-in shopper they allow.
window._ABConfig.bundle_rules. On stores where rules arrive after the page loads, call it inside whenConfigReady, and for a product other than the one on the page, after await loadAllBundleRules(). Called earlier, it returns no discount and starts loading the missing rules.
It returns an empty array, and logs an error to the console, when variantId is missing or the merchant has turned on Disable App Functionality. The Shopify Function applies the discount at checkout, so treat the result as a preview. See Previewing the discount.
Events
Bundles dispatches these events on a Shopify theme. Each one names the target to listen on.appstle-bundles:rules-loadedis dispatched ondocument, with noevent.detail. It fires when the embed has filledwindow._ABConfig.bundle_ruleswith rules it fetched after the page loaded.appstle-bundles:reconcileis dispatched onwindow, withevent.detailset to{ state, id }. It fires when Bundles checks the cart after a change, to keep bundle quantities and discounts in step. See Cart reconcile.appstleBundlesAppliedVolumeDiscountis dispatched ondocumentby the volume discount widget on a product page. It fires when the widget renders and each time the selected tier, its quantity or the variant changes, withevent.detailset to the selected tier.event.detailisnullwhen no tier is selected or the widget goes away. Only widgets whose Widget theme version is Modern, the default, send it; Legacy widgets don’t. See Applied volume discount.- To get the current tier at any time, dispatch
appstleBundlesAppliedVolumeDiscount:requestondocument. The widget answers withappstleBundlesAppliedVolumeDiscount. appstle-bundle-product-added-to-cartis dispatched on bothdocumentandwindow, withevent.detailset to the bundle’suniqueRef(a string). It fires when a fixed pricing bundle widget’s own button has added the bundle to the cart. Right after it, the widget follows the bundle’s redirect setting: it goes to the cart, checkout or a custom URL, or opens or refreshes the cart drawer. The page can unload as soon as your listener returns, so keep the listener synchronous, and send analytics withnavigator.sendBeacon. It doesn’t fire when the theme’s add-to-cart button adds a bundle from a product page.AppstleBundles:BuildABox:OpenAppstleCartDraweris dispatched onwindow, with noevent.detail. It fires when a shopper clicks a link to/carton a page that runs the Bundles widget script, while the Appstle Slider Cart is off. See Cart links on bundle widget pages.
Applied volume discount
For example, the volume discount widget on a product page sends thisevent.detail after the shopper chooses the 3 items tier (15 percent):
For a Buy X Get Y tier,
quantity and value are X, the quantity to buy, discountType is PERCENTAGE, and discount is the free share of the X plus Y units: Y ÷ (X + Y) × 100. Fields that aren’t in the table, such as isSubscriptionFeatureEnabled, aren’t documented.
Cart reconcile
On stores with an active volume discount, combo or fixed pricing bundle, Bundles checks the cart after each cart change and can change it again so bundle quantities and discounts stay in step. It dispatchesappstle-bundles:reconcile on window around that check, so a cart drawer can wait for it before it re-reads the cart.
event.detail.stateisscheduledwhen a check is coming, anddonewhen it has finished, whether or not it changed the cart.event.detail.idis a string that identifies the check. Cart changes made in quick succession share oneid.
fetch: a POST to /cart/add.js, /cart/change.js or /cart/update.js, with the URL as a string. Changes made with XMLHttpRequest don’t start a check.
Re-read the cart on done, which usually arrives a few seconds after scheduled. If it hasn’t arrived 15 seconds after scheduled, re-read the cart anyway.
Cart links on bundle widget pages
On a page that runs the Bundles widget script, such as a page with a fixed pricing, dynamic pricing or sectioned bundle widget, the script cancels the click on every link whosehref is exactly /cart and that’s on the page when the script loads. It dispatches AppstleBundles:BuildABox:OpenAppstleCartDrawer on window instead.
The shopper goes to the cart page only when the Enable Redirect to Cart Page On Cart Icon Click setting on the Bundle Configurations page is on (bundle_setting.redirectToCartPageOnCartIconClick). With the setting off, the click does nothing until a listener opens a cart. To open your theme’s cart drawer, listen for the event:
Cart refresh helpers
Bundles changes the cart in the background, so your theme’s cart doesn’t know about the change. After it does, it calls these helpers to make the theme re-read the cart. They’re best-effort: they guess which cart and drawer your theme uses. If you control your theme’s cart, refresh it yourself when you hear the events below. You can also call the helpers after your own code changes the cart in the background. These methods are onwindow._ABConfig from DOMContentLoaded.
triggerCartRefresh(): returns a promise. It dispatches the cart events below, re-renders the theme’s cart drawer and cart count from Shopify, and on the cart page re-renders the cart sections. On the cart page, it reloads the page when it can’t find the cart sections.openCartDrawer(): opens the theme’s cart drawer. It does nothing on the cart page. On a theme without a cart drawer, it can click the header’s cart link, which takes the shopper to the cart page.refreshCartDrawer(): returns a promise. It callstriggerCartRefresh(), then, if the drawer is open, opens it again so it shows the new cart.
triggerCartRefresh() re-reads the slider cart without dispatching the events below, openCartDrawer() opens the slider cart, and refreshCartDrawer() re-reads it and opens it when the slider cart is set to open after an add.
Without the slider cart, triggerCartRefresh() dispatches each of these events on document and on document.body, with bubbles: true and event.detail set to { data: {} }:
cart:refresh, cart:updated, cart-update, cart:change, cart:update, theme:cart:change, theme:cart:update and dispatch:cart-drawer:open.
When jQuery is on the page, it also triggers cart.requestComplete and cart:updated on $(document). Many themes listen for one of these names, so their cart refreshes without code from you.
Bundles widgets also dispatch cart:refresh, and some cart:updated, after they change the cart. They use window, document or document.documentElement, each with a different event.detail or none. All of them reach a listener on window, so listen there, and don’t rely on event.detail.
Slider cart
The slider cart is the cart drawer that the Appstle Slider Cart app embed adds to a theme. These interfaces exist only when that embed is on.Slider cart events
The slider cart dispatches these events ondocument. Every event.detail includes timestamp, in milliseconds since the epoch.
cart has the shape of Shopify’s /cart.js response, and item the shape of one of its items, with money in cents.
On the cart page, closing the drawer takes the shopper to the last store page they visited in that browser tab, or to the store’s home page, and no close event fires.
To change the drawer’s language without a reload, dispatch appstle-slider-cart:locale-changed on document with detail.locale set to a locale your store publishes, for example fr. The drawer’s built-in labels, such as the subtotal and the checkout button, switch to that language.
window.appstleSliderCartApi
The slider cart sets window.appstleSliderCartApi.
openCart(options): opens the drawer. Every option is optional:refetchCart(boolean) re-reads the cart as the drawer opens,source(string) becomes the open event’scartOpenSource, andfocusReturnAnchor(element) gets focus back when the drawer closes.closeCart(): closes the drawer.refreshCart(): re-reads the cart.getCartData(): returns the cart the drawer last read.updateTieredPromotionsBar(): re-reads the cart and updates the tiered promotions bar. Call it after your own code changes the cart. It does nothing when the bar is off.setCheckoutButtonClickHandler(handler): makes the checkout button callhandler(cart)and wait for it. Returnfalseto stop the checkout, for example when your own validation fails. Passnullto remove the handler.getCheckoutButtonClickHandler(): returns the current handler.
Next steps
Build your own frontend
Read bundle configuration and add bundles to the cart from your own storefront.
API changelog
Dated changes to the Bundles APIs and JavaScript events.