Skip to content

Liquid / Page Builders

The Kaching Bundles widget automatically appears above the Add to Cart button on product pages — no code changes required. The rest of this guide covers integrations where you need more control: custom placement, variant syncing, cart handling, and reading widget state.

By default the widget auto-places itself above the Add to Cart button. To control where it renders — for example inside a page builder section or below the product title — add this element wherever you want the widget:

<kaching-bundle product-id="{{ product.id }}"></kaching-bundle>

When the widget finds a <kaching-bundle> element on the page, it skips auto-placement and renders inside your custom element instead.

If you need to delay initialization — for example, until after your page builder has rendered — set this flag before the Kaching script loads:

window.kachingBundlesDisableAutoInitialize = true

Then call window.kachingBundlesInitialize() manually when ready:

window.kachingBundlesInitialize()

Fired after the widget is fully mounted and ready. Useful for page builders that need to run code after the widget is available.

document.addEventListener("kaching-bundles-block-loaded", (event) => {
const { component } = event.detail
// `component` is the <kaching-bundles-block> HTMLElement
})

If your page builder has its own variant picker, keep the Kaching widget in sync by setting currentVariantId whenever the selected variant changes:

const widget = document.querySelector("kaching-bundles-block")
widget.currentVariantId = selectedVariant.id

currentVariantId accepts a numeric Shopify variant ID.

The widget fires a variant-selected event when the customer selects a variant inside the widget (e.g., when a bundle includes variant options). Use this to update product images or other UI:

widget.addEventListener("variant-selected", (event) => {
const { variantId } = event.detail
updateProductImages(variantId)
})

If your theme has its own quantity picker, keep the widget in sync by setting quantity whenever the value changes:

const widget = document.querySelector("kaching-bundles-block")
widget.quantity = quantityInput.value

By default, the widget hides the theme’s native quantity input to avoid a duplicate UI. To keep it visible, set this flag before the Kaching script loads:

window.kachingBundlesKeepQuantityInput = true

Some page builders don’t have a native Shopify product form for the widget to read the selling plan from. To set the selling plan explicitly, set sellingPlanId on the widget directly:

const widget = document.querySelector("kaching-bundles-block")
widget.sellingPlanId = 12345678

sellingPlanId accepts a numeric Shopify selling plan ID. When set, the widget uses this selling plan for pricing and includes it in the items returned by .items().

To clear the selling plan:

widget.sellingPlanId = undefined

Use the .pricing() method to read current pricing for a one-time display — for example on a custom ATC button:

const widget = document.querySelector("kaching-bundles-block")
const { discountedPrice, fullPrice } = widget.pricing()
atcButton.textContent = `Add to Cart — ${discountedPrice.formatted}`
widget.pricing(): Pricing
{
discountedPrice: { amount: number; formatted: string }
fullPrice: { amount: number; formatted: string }
discountedPricePerItem: { amount: number; formatted: string }
fullPricePerItem: { amount: number; formatted: string }
discountedPriceWithoutSellingPlan: { amount: number; formatted: string }
discountedPricesForSellingPlans: Array<{
sellingPlanId: number
amount: number
formatted: string
}>
}

formatted strings use the store’s currency format (e.g., "$29.99"). Amounts are in currency units (e.g., 29.99 for $29.99).

For reactive updates, listen to the items-changed event — see Reacting to selection changes.

Fired whenever the selection changes — when the customer picks a different tier, changes quantity, or selects bundle products. The event carries no detail; call .pricing() or .items() to read the current state:

const widget = document.querySelector("kaching-bundles-block")
widget.addEventListener("items-changed", () => {
const { discountedPrice } = widget.pricing()
atcButton.textContent = `Add to Cart — ${discountedPrice.formatted}`
})

Fired when a deal bar tier is selected or deselected.

const widget = document.querySelector("kaching-bundles-block")
widget.addEventListener("deal-bar-selected", (event) => {
const { dealBarId, preselected } = event.detail
if (dealBarId) {
console.log("Tier selected:", dealBarId)
} else {
console.log("Tier deselected")
}
})
detail: {
dealBarId: string | null // null when deselected
preselected: boolean // true if the tier was auto-selected
}

By default, the widget handles adding items to the cart — and we strongly recommend keeping it that way. The built-in handling covers edge cases like selling plans, cart properties, and discount validation that are easy to get wrong.

Only disable it if your page builder absolutely must manage the cart itself.

Set this flag before the Kaching script loads:

window.kachingBundlesDisableAddToCartHandling = true

Before adding to cart, validate that the customer has made a valid selection:

const widget = document.querySelector("kaching-bundles-block")
const { valid, message } = widget.validateItemSelection()
if (!valid) {
// The widget already shows the error UI — optionally show `message` in your own UI too
return
}
widget.validateItemSelection(): { valid: boolean; message: string | null }

If valid is false, message contains the merchant-configured error text (or null if none is set).

For silent checks (e.g., disabling a button without showing error UI), use .isItemSelectionValid() instead:

widget.isItemSelectionValid(): boolean

Use .validateItemSelection() when the customer attempts to add to cart, and .isItemSelectionValid() for silent checks.

When the customer clicks your ATC button, call .items() on the widget to get the cart line items:

const widget = document.querySelector("kaching-bundles-block")
const lines = widget.items()
widget.items(): Item[]

Each item has the following shape:

{
id: number // Shopify variant ID
quantity: number
properties: Record<string, unknown>
selling_plan?: number // Selling plan ID, if subscriptions apply
}

Pass lines to your cart API or page builder’s cart action.

Some widget interactions submit to the cart directly instead of waiting for your ATC button — for example, the “One-time purchase” / “Subscribe & save” toggle in the link-style subscription layout, where clicking the link is itself the add-to-cart action.

When this happens, the widget fires an add-to-cart-requested event on itself. Listen for it and forward the items to your cart:

const widget = document.querySelector("kaching-bundles-block")
widget.addEventListener("add-to-cart-requested", (event) => {
const { items } = event.detail
addToCart(items)
})
detail: {
items: Item[] // same shape as widget.items()
}

The items array already reflects the chosen selling plan — for example, when the customer clicks “One-time purchase”, selling_plan is omitted from each item; when they click “Subscribe & save”, it is set to the selected plan ID. Pass the array straight through to your cart action without re-deriving the plan.

If you don’t handle this event, widget-initiated paths will appear broken: the customer clicks the toggle, the widget dispatches the request, but nothing reaches the cart.