Skip to main content
On a Shopify theme, the Appstle Bundles app embed and the Appstle Slider Cart app embed expose a small set of JavaScript globals, methods and events. This page lists each one, when it fires and what it carries. Use them in your theme or in a script you add to the storefront, for example to read bundle rules, to re-read the cart after Bundles changes it, or to react when the slider cart opens. These interfaces exist only on a Shopify theme where the app embed is on. A headless storefront doesn’t run Appstle’s scripts: read the rules from the shop metafield and add bundles to the cart with Shopify’s APIs, as Build your own frontend explains.
Build on these globals, methods and events, not on the widgets’ markup. The HTML structure, CSS class names and element IDs of the Bundles widgets aren’t part of this interface. They can change whenever a widget’s design is updated, so code or CSS that selects widget elements can stop working after an update. For changes to these globals, methods and events, see the API changelog. If you need full control of the markup, build your own frontend.

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._ABConfig doesn’t exist yet while scripts earlier in the page run. Run your code from a script with defer, or in a DOMContentLoaded listener.
  • 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._ABConfig is undefined. Check for it before you call a method, for example with window._ABConfig?.whenConfigReady(...).

Read bundle configuration

The Bundles app embed sets window._ABConfig. These are its documented members:
  • bundle_rules (array): the store’s active bundle rules that the page has, in the same shape as the appstle_bundles.bundle_rules shop 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): runs callback once 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): returns true when the page has the rules for every product in productIds, 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 with window._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) runs callback once 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 runs callback, with bundle_rules empty and only the labels in bundle_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 dispatches appstle-bundles:rules-loaded on document. Don’t replace the array or the object, and read them again after the event.
To read the rules for products other than the one on the page, wait for all of them:
On a page that loaded with every rule, 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.
It reads 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-loaded is dispatched on document, with no event.detail. It fires when the embed has filled window._ABConfig.bundle_rules with rules it fetched after the page loaded.
  • appstle-bundles:reconcile is dispatched on window, with event.detail set to { state, id }. It fires when Bundles checks the cart after a change, to keep bundle quantities and discounts in step. See Cart reconcile.
  • appstleBundlesAppliedVolumeDiscount is dispatched on document by 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, with event.detail set to the selected tier. event.detail is null when 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:request on document. The widget answers with appstleBundlesAppliedVolumeDiscount.
  • appstle-bundle-product-added-to-cart is dispatched on both document and window, with event.detail set to the bundle’s uniqueRef (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 with navigator.sendBeacon. It doesn’t fire when the theme’s add-to-cart button adds a bundle from a product page.
  • AppstleBundles:BuildABox:OpenAppstleCartDrawer is dispatched on window, with no event.detail. It fires when a shopper clicks a link to /cart on 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 this event.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 dispatches appstle-bundles:reconcile on window around that check, so a cart drawer can wait for it before it re-reads the cart.
  • event.detail.state is scheduled when a check is coming, and done when it has finished, whether or not it changed the cart.
  • event.detail.id is a string that identifies the check. Cart changes made in quick succession share one id.
Bundles sees cart changes made with 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.
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 whose href 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:
When the Appstle Slider Cart is on, it handles clicks on cart links itself: it opens its drawer, the shopper stays on the page, and you don’t need this listener.

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 on window._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 calls triggerCartRefresh(), then, if the drawer is open, opens it again so it shows the new cart.
When the Appstle Slider Cart embed is on, the slider cart replaces all three: 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 on document. 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’s cartOpenSource, and focusReturnAnchor (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 call handler(cart) and wait for it. Return false to stop the checkout, for example when your own validation fails. Pass null to 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.