# Welcome to Elite Bundle Builder

Merchant help center for Elite Bundle Builder, a Shopify bundle builder app for build-your-own bundles, multi-option bundles, drag-and-drop bundle pages, automatic discounts, and bundle analytics.

Elite Bundle Builder is a Shopify bundle builder app for merchants who want to create high-converting product bundles without custom code. Use it to launch build-your-own bundles, mix-and-match bundles, multi-option bundles, bundle discounts, mobile-friendly storefront experiences, and bundle analytics from one app.

This help center explains how to install the app, create bundles, customize the storefront design, publish bundles, configure automatic discounts, track performance, and troubleshoot common issues.

Install the app from the Shopify App Store: [Elite Bundle Builder](https://apps.shopify.com/elite-bundle-builder).

## Why Merchants Use Elite Bundle Builder

Elite Bundle Builder is built for Shopify stores that want a flexible, design-focused bundle experience:

* **Build Your Own Bundle** flows for gift boxes, product routines, sample packs, meal kits, and mix-and-match offers.
* **Multi-Option Bundles** for fixed product sets where customers choose variants or options for each bundled item.
* **Drag-and-drop bundle design** so every bundle page can match the storefront brand.
* **Automatic tiered discounts** that apply at checkout without discount codes.
* **Mobile-optimized bundle pages** designed for customers shopping on phones, tablets, and desktop.
* **Storefront-ready publishing** through the Elite Embed Core app embed.
* **Bundle analytics** for views, orders, revenue, conversion, average order value, and top-performing bundles.

## Best For

Elite Bundle Builder is a strong fit for Shopify merchants selling:

* Beauty routines and skincare kits
* Fashion outfits and matching sets
* Food, snack, and beverage boxes
* Wellness and supplement bundles
* Gift boxes and holiday bundles
* Trial packs and sample sets
* Product kits with size, color, or variant choices

## Start Here

If this is your first time using the app, follow this order:

1. [Introduction](/overview/introduction)
2. [Key Features](/overview/key-features)
3. [Getting Started](/installation-and-requirements/getting-started)
4. [Create Your First Bundle](/create-bundles/create-your-first-bundle)
5. [Design and Customize](/customize-and-publish/design-and-customize)
6. [Publish on Your Storefront](/customize-and-publish/publish-on-storefront)

## Support

Open the app and click **Support** or **Chat now** from the dashboard or settings page. Typical response time is around 10 minutes during IST business hours, with a maximum wait of 1 business day.


# Introduction

Learn what Elite Bundle Builder is, who it is for, and how Shopify merchants use it to create customizable product bundles.

Elite Bundle Builder is a Shopify bundle builder app for creating customizable bundle experiences that help customers buy more products together. Merchants can create build-your-own bundles, multi-option bundles, automatic discounts, and custom bundle pages without editing theme code.

The app is designed for stores that want bundle pages to feel like part of the brand, not like a generic widget. Each bundle can have its own products, options, discounts, design, and performance analytics.

## What You Can Build

Use Elite Bundle Builder to create:

* Build-your-own boxes
* Mix-and-match product bundles
* Gift bundles
* Product routine builders
* Sample packs
* Outfit builders
* Fixed product kits with selectable variants
* Multi-option combo bundles

## How It Works

Most merchants launch a bundle in this flow:

1. Choose a bundle type.
2. Add products, collections, or options.
3. Configure quantity rules and discounts.
4. Customize the bundle page with the drag-and-drop editor.
5. Enable the Elite Embed Core app embed.
6. Preview the storefront experience.
7. Set the bundle live.

## Why Bundle Pages Matter

A good bundle page should make buying easier. It should help customers understand what to choose, show progress clearly, explain discounts, and make the add-to-cart action obvious on every device.

Elite Bundle Builder gives merchants control over both the selling logic and the storefront design, so bundles can be optimized for conversion, average order value, and brand consistency.

## Recommended Next Pages

* [Key Features](/overview/key-features)
* [Bundle Types](/overview/bundle-types)
* [Getting Started](/installation-and-requirements/getting-started)


# Key Features

Key features of Elite Bundle Builder for Shopify, including build-your-own bundles, multi-option bundles, drag-and-drop design, automatic discounts, mobile optimization, and analytics.

Elite Bundle Builder is designed to be a powerful Shopify bundle builder app for merchants who want flexible bundle logic, brand-matched design, and checkout-ready discounts.

## Build-Your-Own Bundle Builder

Create guided bundle flows where customers choose products step by step. This is ideal for mix-and-match offers, gift boxes, routines, trial packs, and curated sets.

Merchants can:

* Add multiple bundle steps
* Use specific products or collections
* Set minimum and maximum quantities
* Allow only one of each product
* Show variants as separate selectable products

## Multi-Option Bundle Builder

Create fixed combo bundles where each option can have selectable products or variants. This works well for outfits, kits, matching sets, and bundles where every option must be completed.

If products or variants go out of stock, unavailable choices are blocked. If all choices in a required option are unavailable, the bundle cannot be purchased until inventory is restored or the option is updated.

## Drag-and-Drop Bundle Design

Each bundle can have a custom storefront design. Use the visual editor to drag, reorder, and style layout, commerce, and content blocks.

Design controls include:

* Containers, columns, grids, and stacked layouts
* Product selection areas
* Side cart or selected-items sections
* Discount displays
* Headings, text, images, buttons, dividers, and videos
* Colors, typography, spacing, borders, shadows, and mobile behavior

## Mobile-Optimized Storefront Experience

Bundle pages are designed for customers shopping across desktop, tablet, and mobile. Merchants can preview layouts and adjust structure so customers can select products, review their bundle, and add to cart from any device.

## Automatic Bundle Discounts

Create tiered discounts that apply automatically at checkout. Customers do not need discount codes.

Supported discount approaches include:

* Percentage off
* Fixed amount off
* Fixed bundle price
* Amount off per item
* Quantity-based tiers
* Order-total-based tiers

## Shopify Storefront Publishing

Bundles publish through Shopify storefront pages and the Elite Embed Core app embed. Merchants can use product-page rendering for a native product-page experience or dedicated-page rendering for focused campaigns and landing pages.

## Designed for Live Storefront Traffic

Elite Bundle Builder is designed for real Shopify storefront use, including active product pages, campaign traffic, and mobile shoppers. Merchants should still preview bundles on their live theme before launch, especially after major theme or design changes.

## Bundle Analytics

Track how bundles perform with merchant-friendly analytics:

* Views
* Orders
* Revenue
* Conversion rate
* Average order value
* Views over time
* Orders over time
* Revenue over time
* Top bundles by revenue
* Top bundles by conversion rate

## Built for Real Store Operations

Elite Bundle Builder supports practical merchant workflows:

* Draft, unlisted, and active bundle statuses
* Duplicate bundles for campaigns or variants
* Preview before publishing
* Elite Embed Core app embed setup
* Settings and setup status checks
* Merchant support through in-app chat


# Bundle Types

Compare Build Your Own Bundle, Multi-Option Bundle, and coming subscription bundle options in Elite Bundle Builder for Shopify.

Elite Bundle Builder supports different bundle formats for different selling strategies.

## Build Your Own Bundle

Use **Build Your Own Bundle** when customers should choose products across multiple steps.

Best for:

* Gift boxes
* Routine builders
* Meal kits
* Sample packs
* Mix-and-match sets
* "Choose 3" or "Pick 5" offers

Example flow:

1. Choose your base product.
2. Pick two add-ons.
3. Select a bonus item.
4. Add the completed bundle to cart.

## Multi-Option Bundle

Use **Multi-Option Bundle** when you want to sell a fixed group of products while letting customers choose variants or options for each item.

Best for:

* Outfit combos
* Matching sets
* Product kits with size or color choices
* Bundles where every component should be included

Example flow:

1. Customer chooses a jacket size.
2. Customer chooses trouser size.
3. Customer selects color options.
4. The complete set is added to cart.

## Subscription Bundle

Subscription bundles are marked as coming soon in the app. Use Build Your Own Bundle or Multi-Option Bundle for current launches.

## Which Type Should You Pick?

| Goal                                           | Recommended type      |
| ---------------------------------------------- | --------------------- |
| Customer builds their own box                  | Build Your Own Bundle |
| Customer chooses from several product groups   | Build Your Own Bundle |
| Customer buys a fixed kit with variant choices | Multi-Option Bundle   |
| You want to test a simple offer quickly        | Build Your Own Bundle |
| You need recurring subscriptions               | Coming soon           |

## Best Practice

Start with the simplest bundle type that matches the offer. A clear bundle with fewer choices usually launches faster and is easier to improve with analytics.


# Getting Started

Install Elite Bundle Builder, set up theme integration, and launch your first bundle in your Shopify store.

This guide covers the first steps after installing Elite Bundle Builder.

## Install the App

Install Elite Bundle Builder from Shopify and approve the requested permissions. The app needs access to create bundle products, configure storefront rendering, apply automatic discounts, and record bundle analytics.

After installation, the app opens inside your Shopify admin.

## First-Time Setup Checklist

Complete these steps in order:

1. Open **Elite Bundle Builder** from your Shopify admin apps list.
2. Click **Create bundle**.
3. Choose a bundle type.
4. Add products or collections to the bundle.
5. Save the bundle.
6. Enable the Elite Embed Core app embed.
7. Preview the bundle on your storefront.
8. Set the bundle status to **Active** when ready.

## Theme Integration Setup

Bundles need the Elite Embed Core app embed to render on the storefront and run cart protection.

### Required: Elite Embed Core

All bundle operations use the same Shopify app embed.

1. Open **Online Store > Themes** in Shopify.
2. Click **Customize** on your current theme.
3. Open **App embeds**.
4. Enable **Elite Embed Core**.
5. Click **Save**.

You only need to enable this once per theme.

### Optional: Multi-Option Bundle Block

MOB bundles can also use the **Multi-Option Bundle** app block if you want to position the selector in a specific place on a product template.

1. Open **Online Store > Themes** in Shopify.
2. Click **Customize** on your current theme.
3. Navigate to a **product template**.
4. In the left sidebar, click **Add section** or find the app block area.
5. Add the **Multi-Option Bundle** app block.
6. Click **Save**.

## Bundle Statuses

Bundles can have three statuses:

| Status   | What it means            | Best use                     |
| -------- | ------------------------ | ---------------------------- |
| Draft    | Not visible to customers | Work in progress             |
| Unlisted | Available by direct link | Testing or private campaigns |
| Active   | Available publicly       | Published bundles            |

Use **Unlisted** when testing a new bundle before promoting it.

## Your First Success Check

You are ready to move on when:

* You can see the app dashboard.
* The setup guide shows your first bundle task.
* The Elite Embed Core app embed is enabled.
* You know which products or collections you want to bundle.


# Requirements and Compatibility

Requirements and compatibility notes for using Elite Bundle Builder with Shopify stores and Online Store 2.0 themes.

Elite Bundle Builder is built for Shopify merchants who want to create and publish bundle experiences from the Shopify admin.

## Requirements

Before installing and using the app, make sure you have:

* A Shopify store
* Access to the Shopify admin
* Permission to install apps
* Permission to edit the current theme
* Products or collections ready to include in bundles
* An Online Store 2.0 compatible theme for core app embed publishing

## Theme Compatibility

Elite Bundle Builder uses the Elite Embed Core Shopify app embed to display bundle experiences and run cart protection on the storefront. Most Online Store 2.0 themes support app embeds.

After switching or publishing a new theme, confirm the Elite Embed Core app embed is enabled on the new live theme.

## Device Compatibility

Bundle pages are designed for desktop, tablet, and mobile shoppers. Always preview your bundle on mobile before launch, especially after major design changes.

## Checkout Compatibility

Configured bundle discounts apply automatically at checkout when the customer's selection qualifies. Customers do not need to enter a discount code.

## Inventory Behavior

Unavailable products or variants are marked as out of stock and cannot be purchased. For Multi-Option Bundles, if every choice in a required option is unavailable, the whole bundle becomes unavailable until that option is updated or inventory returns.

## No-Code Setup

Normal setup does not require editing theme code. Merchants configure bundles in the app and enable the Elite Embed Core app embed in Shopify.


# Settings

Configure Elite Bundle Builder settings, setup status, product-page or dedicated-page bundle display, and support options.

The settings page shows whether the core services required for bundles are set up and lets you choose how bundles display on your storefront.

## Setup Status

The setup section can show the status of:

* Storefront token
* Cart transform
* Discount
* Web pixel

These services are created automatically when the app is installed and authenticated.

If one of these shows **Failed**, contact support. Most merchants do not need to manually configure these services.

## Bundle Display

The **Bundle display** setting controls where customers see the bundle builder.

### Product Page

Recommended for most stores.

In this mode, the bundle builder appears on the bundle product page and replaces the normal product-page content.

Use this when:

* You want a simple launch.
* You want bundle links to behave like normal product links.
* You want customers to understand they are buying a Shopify product.

### Dedicated Page

In this mode, the bundle opens on a standalone bundle page.

Use this when:

* You want a more focused campaign page.
* You want to send traffic directly from ads or email.
* You do not want the normal product page layout around the bundle.

## Support

The settings page includes support access. Use **Chat now** when:

* A setup service shows failed.
* The storefront does not render the bundle.
* Checkout discounts are not applying.
* Analytics look incorrect.
* You need help choosing a display mode.

Typical response time is around 10 minutes during IST business hours, with a maximum wait of 1 business day.


# Create Your First Bundle

Step-by-step guide to creating your first bundle in Elite Bundle Builder, including naming, product selection, discounts, preview, and launch checklist.

This guide walks through creating a bundle from the app dashboard.

## Step 1: Start a New Bundle

1. Open **Elite Bundle Builder** in your Shopify admin.
2. Click **Create bundle**.
3. Choose the bundle type you want to create.

For most stores, **Build Your Own Bundle** is a good starting point because it gives customers a guided step-by-step buying experience. **Multi-Option Bundle** works well when you want to sell a fixed kit with variant choices.

## Step 2: Name the Bundle

Choose a clear customer-facing name, such as:

* Build Your Skincare Routine
* Create Your Snack Box
* Complete the Look
* Make Your Own Gift Set

Avoid internal names like "Bundle Test 1" unless the bundle is staying in draft.

## Step 3: Add Bundle Steps or Options

The next step depends on your bundle type:

* **Build Your Own Bundle**: Add steps. Each step is one decision the customer makes (for example, "Choose your cleanser" or "Pick your drink").
* **Multi-Option Bundle**: Add options. Each option is a product group the customer chooses from (for example, "Jacket" or "Trousers").

Keep the first bundle simple. Two or three steps or options are usually easier to test than a long flow.

## Step 4: Choose Products or Collections

For each step or option, choose whether customers select from specific products or a collection.

Use products when:

* You want strict control over exactly what appears.
* The bundle is seasonal or limited.
* You only have a few choices.

Use collections when:

* You restock or rotate products often.
* New products should appear automatically.
* You want easier long-term maintenance.

## Step 5: Set Advanced Rules

Open the advanced settings on each step or option to control how customers can select products.

Available advanced rules include:

* Limit selection quantity
* Set minimum quantity
* Set maximum quantity
* Allow only one of each product
* Show variants as separate products

Use quantity limits to control how many items customers can choose.

Examples:

| Bundle           | Step               | Suggested rule |
| ---------------- | ------------------ | -------------- |
| Skincare routine | Choose cleanser    | Min 1, max 1   |
| Snack box        | Pick snacks        | Min 3, max 8   |
| Outfit builder   | Choose accessories | Min 0, max 3   |

If a step or option is required, set the minimum quantity to at least 1.

## Step 6: Configure Discounts

Open the **Discount** section to add bundle discounts. Discounts are optional, but they can encourage customers to add more items.

Common discount setups include:

* Percentage off after a customer selects a certain number of items
* Fixed amount off after the bundle reaches a cart value
* A fixed bundle price for qualifying selections
* A per-item discount for larger bundles

Example:

| Customer selects | Discount |
| ---------------- | -------- |
| 3 items          | 5% off   |
| 5 items          | 10% off  |
| 8 items          | 15% off  |

Discounts apply automatically at checkout when the customer qualifies. Customers do not need to enter a discount code.

## Step 7: Save and Preview

Save the bundle, then use the view or preview action from the bundle dashboard. If you are still testing, keep the status as **Unlisted** and share the direct preview link internally.

Make sure your theme integration is set up:

* Enable the **Elite Embed Core** app embed in **Online Store > Themes > Customize > App embeds**.
* For **Multi-Option Bundle**, optionally add the **Multi-Option Bundle** app block only if you want to position the selector in a specific product-page area.

Without it, the bundle page will not load the bundle experience even if the bundle status is Active or Unlisted.

## Launch Checklist

Before publishing, confirm:

* The bundle name is customer-friendly.
* Every step or option has products or a collection.
* Required steps or options have minimum quantities.
* Product images and variant options look correct.
* The Elite Embed Core app embed is enabled in your theme.
* Discounts, if used, apply correctly at checkout.


# Build Your Own Bundle

Complete guide to creating Build Your Own Bundles in Elite Bundle Builder, including steps, products, discounts, design customization, and FAQ.

A **Build Your Own Bundle** guides customers through a step-by-step flow where they pick products across multiple steps. The app adds all selected items to the cart as a single bundle.

## What It Is Best For

* Gift boxes
* Routine builders
* Meal kits
* Sample packs
* Mix-and-match sets
* "Choose 3" or "Pick 5" offers

## How the Customer Experience Works

1. Customer lands on the bundle page.
2. They see the first step (for example, "Choose your base").
3. They select products, optionally picking variants like size or color.
4. They move through each step, adding items to their running selection.
5. A side cart or selected-items area shows what they have picked so far.
6. They click **Add bundle to cart** when finished.
7. Discounts apply automatically at checkout if the bundle qualifies.

## Before You Start

The Elite Embed Core app embed must be enabled in your Shopify theme for bundles to render on the storefront.

1. In Shopify, go to **Online Store > Themes**.
2. Click **Customize** on your current theme.
3. Open **App embeds**.
4. Turn on **Elite Embed Core**.
5. Click **Save**.

If the embed is off, customers will see a standard product page instead of the bundle builder.

***

## Bundle Setup Sections

When you create or edit a Build Your Own Bundle, the admin is organized into three tabs: **Steps**, **Discounts**, and **Design**.

### Steps Tab

This is where you define what customers choose and in what order.

#### Step Title

Give each step a short, action-based name. Good titles tell the customer exactly what to do:

* Choose your cleanser
* Pick your snacks
* Select your top
* Add your extras

#### Product Source

For each step, choose where the selectable products come from:

* **Specific products**: You manually pick which products appear. Use this when you want full control.
* **Collection**: You choose one Shopify collection. The step automatically shows all products from that collection. Use this when you want the step to update automatically as your catalog changes.

#### Adding and Removing Steps

* Click **Add step** to create a new step.
* Use the **drag handle** on a step to reorder steps.
* Click the **delete** button on a step to remove it.
* A bundle must have at least one step.

***

### Advanced Settings per Step

Open the settings panel on any step to configure advanced rules.

#### Limit Selection Quantity

Enable this to set a minimum and maximum number of items customers can select in this step.

| Use case                  | Minimum | Maximum |
| ------------------------- | ------- | ------- |
| Required single selection | 1       | 1       |
| Optional extras           | 0       | 3       |
| Build-a-box core          | 3       | 8       |

If a step is required, set the minimum to at least 1.

#### Allow Only One of Each Product

Prevents customers from adding multiple quantities of the same product within the same step. Useful when each product should be chosen at most once.

#### Show Variants as Separate Products

When enabled, each variant (for example, size or color) appears as its own selectable card instead of being grouped under one product with a variant selector. Useful when you want every variant to feel like a distinct choice.

***

### Discounts Tab

Build Your Own Bundles support tiered discounts. You can reward customers based on how much they build.

#### Discount Trigger

Choose what qualifies the customer for a discount tier:

* **Number of items**: The total count of products selected across all steps.
* **Order total value**: The sum of the selected product prices.

#### Discount Calculation

Choose how the discount is calculated:

* **Percentage (%)**: A percentage is deducted from the bundle total.
* **Fixed amount off**: A flat amount is deducted from the bundle total.
* **Fixed target price**: The entire bundle is sold at a fixed price when the tier is reached.
* **Amount off per item**: A fixed amount is deducted for every item in the bundle.

#### Discount Tiers

Add one or more tiers. The app automatically applies the highest tier the customer qualifies for.

Example tier setup:

| Buy     | Get     |
| ------- | ------- |
| 3 items | 5% off  |
| 5 items | 10% off |
| 8 items | 15% off |

Discounts apply automatically at checkout. Customers do not need a discount code.

***

### Design Tab

The design tab lets you pick a template and launch the visual editor. Each BYOB bundle can have its own custom design.

* **Select a template**: Choose a starting layout (for example, Elegant Lifestyle, Serene Wellness, Modern Minimalist).
* **Launch design editor**: Opens the drag-and-drop editor where you customize the bundle page.

You must save the bundle at least once before the design editor becomes available.

***

## Design Editor: Advanced Customization

The BYOB design editor uses a drag-and-drop canvas. You can add, reorder, and configure blocks.

### Available Block Groups

| Group    | Blocks                                       |
| -------- | -------------------------------------------- |
| Layout   | Container                                    |
| Commerce | Step Container, Side Cart, Discount          |
| Content  | Heading, Text, Divider, Button, Image, Video |

### Step Container Block

This block renders the product grid for the current bundle step. It contains **Product Cards** that you can style in detail.

#### Product Card Settings

When you select a Product Card inside the Step Container, the right panel shows these sections:

**Layout**

* **Card Layout**: Vertical or Horizontal
* **Image Width**: Width of the image in horizontal mode

**Image**

* **Image Aspect Ratio**: 1:1, 4:3, 3:4, 16:9, 9:16
* **Image Border Radius**: Rounds the image corners
* **Image Background**: Fallback color behind the image

**Description**

* **Show Description**: Yes or No
* **Description Font Size**, **Color**, **Line Height**

**Card Container**

* **Card Background**, **Padding**, **Margin**
* **Card Border Radius**, **Border Width**, **Border Color**
* **Card Shadow**, **Shadow Blur**, **Shadow Color**

**Content Area**

* **Content Padding**, **Margin**
* **Content Border Width**, **Border Color**

**Title**

* **Title Font Size**, **Font Weight**, **Color**
* **Font Family**, **Line Height**, **Letter Spacing**
* **Text Align**, **Font Style**, **Text Decoration**, **Text Transform**

**Price**

* **Price Font Size**, **Font Weight**, **Color**
* **Compare Price Color**: For strikethrough original prices
* **Font Family**, **Line Height**, **Letter Spacing**
* **Text Align**, **Font Style**, **Text Decoration**, **Text Transform**

**Variants**

* **Show Variant Selector**: Yes or No
* **Variant Type**:
  * **Dropdown**: Each option gets a separate dropdown (for example, one for Color, one for Size).
  * **Buttons**: Each option value appears as a clickable button.
  * **Combined**: All variants are shown in a single dropdown with images (for example, "White / M", "Black / L").

**Button**

* **Button Text**: Label shown on the add-to-cart button. Leave blank to show compact circular +/- quantity buttons instead.
* **Button Background**, **Button Text Color**
* **Button Padding**, **Margin**, **Border Radius**
* **Font Size**, **Font Weight**, **Font Family**
* **Line Height**, **Letter Spacing**, **Text Align**
* **Font Style**, **Text Decoration**, **Text Transform**

### Side Cart Block

Shows the customer's running selection of items across steps. You can customize its position, spacing, and styling.

### Discount Block

Displays discount messaging and progress. It supports three display modes:

* **Progress Bar**: A visual bar showing progress toward the next tier.
* **Segments**: Tier labels shown as segments.
* **Thumbnails**: Visual tier indicators.

For each mode you can customize:

* **Layout Setup**: Flex direction, gap, alignment
* **Container**: Background color
* **Spacing**: Padding and margin
* **Border**: Radius, width, color, style
* **Text**: Font size, weight, color, family, alignment, and styling
* **Progress Bar**: Show/hide, height, background fill color, border radius
* **Tier Style**: Show/hide tiers, active and inactive colors, discount and item label fonts
* **Messages**: Custom text for progress labels, max reached text, and discount labels

***

## FAQ

### Can a customer skip a step?

If a step has a minimum quantity of 0, the customer can skip it. If the minimum is 1 or more, they must select something before adding the bundle to cart.

### What happens if a product goes out of stock?

Out-of-stock products are shown as unavailable in the bundle. Customers cannot add them. If inventory tracking is enabled, the quantity selector also respects available stock.

### Can customers change their mind after selecting a product?

Yes. The Side Cart block shows selected items, and customers can increase, decrease, or remove quantities before adding the bundle to cart.

### How are variants handled?

By default, a product card shows a variant selector so the customer picks the correct size, color, or other option before adding. You can also enable **Show variants as separate products** in the step advanced settings to show each variant as its own card.

### Why is my discount not showing?

* Confirm discount tiers are configured in the **Discounts** tab.
* Confirm the bundle is added to cart from the bundle page, not as individual products.
* Test in checkout, not just on the product page.
* Check the app settings page for any discount service issues.

### Can I use collections for some steps and specific products for others?

Yes. Each step can independently use either specific products or a collection.

### What is the difference between Vertical and Horizontal card layout?

* **Vertical**: Image on top, text and button below. Best for grids.
* **Horizontal**: Image on the left, text and button on the right. Best for compact lists.

### Does the design editor affect the live storefront immediately?

No. You must save the design in the editor, and the bundle must be saved in the admin. Keep the bundle as **Unlisted** while testing the design.

### Can I have different designs for different BYOB bundles?

Yes. Each bundle uses its own template and design state.


# Multi-Option Bundle

Complete guide to creating Multi-Option Bundles in Elite Bundle Builder, including options, single-product mode, discounts, design customization, and FAQ.

A **Multi-Option Bundle** sells a fixed group of products where customers choose one item per option. Each option can be a group of products or a single product with Shopify variant selectors.

## What It Is Best For

* Outfit combos
* Matching sets
* Product kits with size or color choices
* Bundles where every component should be included

## How the Customer Experience Works

1. Customer lands on the bundle product page.
2. They see each option as a labeled group (for example, "Shirt", "Trousers").
3. For each option, they pick exactly one item or variant combination.
4. A price display shows the running total and any applied discount.
5. They use the theme's native **Add to cart** button to purchase.
6. The bundle discount applies automatically at checkout if enabled.

## Before You Start

MOB bundles require the **Elite Embed Core** app embed. The **Multi-Option Bundle** app block is optional and is only needed when you want to position the selector in a specific product-page area.

1. In Shopify, go to **Online Store > Themes**.
2. Click **Customize** on your current theme.
3. Open **App embeds**.
4. Turn on **Elite Embed Core**.
5. Click **Save**.

Later, if you want custom placement, add the optional **Multi-Option Bundle** app block to the product template.

***

## Bundle Setup Sections

When you create or edit a Multi-Option Bundle, the admin is organized into three tabs: **Options**, **Discounts**, and **Design**.

### Options Tab

This is where you define the fixed product groups customers choose from.

#### Option Name

Give each option a clear label that describes the product group:

* Shirt
* Trousers
* Hat
* Shoes

#### Option Mode

Each option can be configured in one of two modes:

* **Products**: Customers pick one product from a list of multiple products you select. Each product's variants are flattened into a single selectable list.
* **Product with options**: You select exactly one product, and customers use Shopify-style cascading option selectors (for example, Color then Size). The app validates that the chosen variant combination exists and is in stock.

#### Adding and Removing Options

* Click **Add option** to create a new option.
* Use the **drag handle** on an option to reorder options.
* Click the **delete** button on an option to remove it.
* A bundle must have at least one option.

### Discounts Tab

Multi-Option Bundles use a simplified discount model. Because the bundle is a fixed combo, tier-based quantity discounts do not apply. Instead, you can apply a single flat discount to the bundle total.

#### Discount Type

* **Percentage off**: Deduct a percentage from the total bundle price (for example, 10% off).
* **Fixed amount off**: Deduct a flat amount from the total bundle price (for example, $5 off).

#### Discount Value

Enter the percentage or amount. The discount applies automatically whenever the bundle is added to cart. There are no tiers or minimums to configure.

***

### Design Tab

The design tab lets you pick a template and launch the visual editor. Each MOB bundle can have its own custom design.

* **Select a template**: MOB bundles use MOB-specific templates (for example, Image Selector or Clean Dropdowns).
* **Launch design editor**: Opens the drag-and-drop editor where you customize the bundle page.

You must save the bundle at least once before the design editor becomes available.

***

## Design Editor: Advanced Customization

The MOB design editor uses a drag-and-drop canvas with blocks tailored for fixed-combo bundles.

### Available Block Groups

| Group   | Blocks                                       |
| ------- | -------------------------------------------- |
| Layout  | Container                                    |
| Bundle  | Option Selector, Price Display, Add to Cart  |
| Content | Heading, Text, Divider, Button, Image, Video |

### Option Selector Block

This block renders each bundle option as a labeled group with selectable items.

#### Layout Settings

* **Display Style**:
  * **Dropdown**: Shows selectable items in a native dropdown list.
  * **Images**: Shows selectable items as horizontal image chips with labels.
* **Gap between options**: Vertical space between each option group.
* **Group padding**: Vertical padding inside each option group.

#### Option Label

* **Color**: Text color of the option title (for example, "Shirt").
* **Font Size**: Size of the option title.
* **Font Weight**: Regular, Medium, Semi-bold, or Bold.

#### Dropdown Style

When **Display Style** is set to **Dropdown**:

* **Border Color**, **Border Radius**, **Background**, **Text Color**
* **Padding**: Inner spacing of the dropdown field.
* **Focus Border Color**: Border color when the dropdown is focused.

#### Image Chip Style

When **Display Style** is set to **Images**:

* **Image Height** and **Image Width**: Dimensions of each chip image.
* **Selected Border**: Border color of the selected chip.
* **Border Radius**: Rounding of the chip corners.

#### Shopify Option Label

When an option uses **Product with options** mode, this controls the labels for cascading variant selectors (for example, "Color", "Size"):

* **Show Option Name**: Yes or No
* **Color**: Text color of the option name label
* **Font Size**: Size of the option name label

***

### Price Display Block

Shows the computed bundle total and any discount badge.

#### Layout

* **Alignment**: Left, Center, or Right
* **Padding Vertical**: Top and bottom spacing

#### Price

* **Color**: Text color of the final price
* **Font Size**: Size of the final price
* **Font Weight**: Regular, Medium, Semi-bold, Bold, or Extra Bold

#### Original Price

* **Show Original Price**: Yes or No
* **Color**: Text color of the strikethrough original price
* **Font Size**: Size of the original price

#### Discount Badge

* **Show Badge**: Yes or No
* **Badge Text**: Use `{percent}` as a placeholder for the discount percentage (for example, "{percent}% OFF").
* **Background**: Badge background color
* **Text Color**: Badge text color
* **Border Radius**: Rounding of the badge corners
* **Font Size**: Size of the badge text

#### Label

* **Show Label**: Yes or No
* **Label Text**: Text shown above the price (for example, "Total").
* **Color** and **Font Size**

***

### Add to Cart Block

This block is invisible on the storefront. It hooks into your theme's native **Add to cart** button and intercepts the form submit so the bundle is added with all selected options. In the editor, it shows a placeholder.

#### Error Message Style

* **Color**: Text color of error messages
* **Font Size**: Size of error messages

***

## FAQ

### Can a customer buy multiple quantities of the same bundle?

Yes. Because the bundle uses the theme's native product page, customers can change the quantity field before adding to cart, subject to inventory limits.

### What happens if a variant combination is out of stock?

For **Product with options** mode, unavailable variant combinations are shown as sold out and cannot be selected. If every variant in an option becomes unavailable, the bundle becomes unavailable.

### Can I mix Products mode and Product with options mode in the same bundle?

Yes. Each option can independently use either mode. For example, Option 1 can be a list of shirts (Products mode), while Option 2 can be a single jacket with size and color selectors (Product with options mode).

### How is the bundle price calculated?

The bundle price is the sum of the selected item prices across all options. If a discount is enabled, it is deducted from this total at checkout.

### Why does the Add to Cart block look like a placeholder in the editor?

The MOB Add to Cart block intentionally does not render its own button. It hooks into your theme's existing **Add to cart** button on the storefront. The placeholder reminds you where the theme button will be intercepted.

### Can I change the option selector from dropdown to images after launch?

Yes. Open the design editor, select the **Option Selector** block, and change the **Display Style** setting. Save the design and preview before making the bundle active.

### What is the difference between Products mode and Product with options mode?

* **Products**: You select multiple products. Customers pick one product (or one variant) from the list. Variant combinations are flattened into a single list.
* **Product with options**: You select exactly one product. Customers use cascading Shopify option selectors to build a valid variant combination. This is best when a single product has many variant combinations.

### Do I need to add an app block for MOB bundles?

No. MOB bundles require the **Elite Embed Core** app embed. Add the **Multi-Option Bundle** app block only when you want to position the bundle selector in a specific place on a product template.

### Can I have a different design for each MOB bundle?

Yes. Each MOB bundle uses its own template and design state.


# Set Up Discounts

Configure automatic Shopify bundle discounts with quantity tiers, order-total tiers, percentage discounts, fixed amount discounts, fixed price bundles, and per-item discounts.

Bundle discounts encourage customers to add more items and increase average order value.

## Discount Types

The app supports tier-based bundle discounts.

You can configure tiers based on:

* Quantity selected
* Bundle order total

You can calculate discounts as:

* Percentage off
* Fixed amount off
* Fixed bundle price
* Amount off per item

## Example Quantity Discount

| Customer selects | Discount |
| ---------------- | -------- |
| 3 items          | 5% off   |
| 5 items          | 10% off  |
| 8 items          | 15% off  |

This works well for snack boxes, sample packs, beauty bundles, and mix-and-match offers.

## Example Order Total Discount

| Bundle total | Discount |
| ------------ | -------- |
| $50          | $5 off   |
| $100         | $15 off  |
| $150         | $30 off  |

This works well when products have different prices and you want the discount based on value instead of item count.

## How Discounts Apply

Discounts apply automatically at checkout. Customers do not need a discount code.

The app calculates the best matching tier based on your settings. If no tier is reached, no bundle discount is applied.

## Best Practices

* Make the first tier easy to reach.
* Keep discount messaging simple.
* Use 2 to 4 tiers, not too many.
* Test the checkout before launch.
* Avoid discounts so large that they reduce margin too much.

## Discount Testing Checklist

Before publishing:

1. Select enough products to reach the first tier.
2. Add the bundle to cart.
3. Go to checkout.
4. Confirm the discount appears.
5. Repeat for the highest tier.
6. Test a cart that does not qualify for a discount.

## Common Discount Issues

If the discount does not appear:

* Confirm the bundle has discount tiers configured.
* Confirm the bundle was added through the bundle page, not as individual products.
* Confirm the app setup status does not show a discount service issue.
* Contact support if the settings page says the discount service failed.


# Design and Customize

Use the drag-and-drop bundle page editor in Elite Bundle Builder to customize Shopify bundle layouts, product grids, discounts, buttons, images, videos, and mobile design.

Elite Bundle Builder includes a drag-and-drop visual editor so you can build a bundle page that feels native to your storefront without custom code.

You are not limited to a fixed template. Start from a template, then drag sections into the canvas, reorder them, adjust spacing, change styling, and save a custom storefront layout for each bundle.

## Open the Design Editor

1. Open the app dashboard.
2. Select the bundle you want to edit.
3. Click **Edit design**.
4. Drag blocks into the canvas or select existing blocks to edit them.
5. Reorder sections from the layers panel when needed.
6. Customize layout, colors, text, spacing, and bundle sections.
7. Save your changes.

## Drag-and-Drop Blocks

The editor includes reusable blocks that you can drag into the bundle design.

For Build Your Own Bundle pages, available block groups include:

| Group    | Blocks                                       |
| -------- | -------------------------------------------- |
| Layout   | Container                                    |
| Commerce | Step Container, Side Cart, Discount          |
| Content  | Heading, Text, Divider, Button, Image, Video |

For Multi-Option Bundle pages, available block groups include:

| Group   | Blocks                                       |
| ------- | -------------------------------------------- |
| Layout  | Container                                    |
| Bundle  | Option Selector, Price Display, Add to Cart  |
| Content | Heading, Text, Divider, Button, Image, Video |

Use containers to structure the page, then place commerce and content blocks inside that layout.

## What You Can Customize

Depending on the bundle type and template, you can customize:

* Page background, width, and padding
* Section layout, including vertical stacks, horizontal rows, columns, and grids
* Section spacing, margins, padding, borders, border radius, shadows, and backgrounds
* Headings and text
* Font size, weight, alignment, line height, color, and text styling
* Product grid layout and step presentation
* Product card appearance
* Cart or selected-items area
* Discount display, progress bar, tier labels, colors, and messages
* Buttons, including text, style, colors, borders, spacing, shadows, and opacity
* Images, including image URL, alt text, sizing, object fit, radius, and shadow
* Videos, including YouTube, Vimeo, or MP4 URLs, sizing, autoplay, loop, mute, and controls
* Mobile layout behavior

The bundle products, options, prices, cart contents, and discount tiers still come from your bundle setup. The design editor controls how those elements are arranged and presented to customers.

## Multilingual Support & Translations

Elite Bundle Builder natively supports translating storefront text directly within the visual design editor for a localized shopping experience.

When editing text fields like headings, buttons, custom labels, and discount badges:

1. Look for the **Translate** button next to supported text fields in the right sidebar.
2. Click the button to open the translation panel.
3. You will see your store's default language alongside any additional published Shopify languages.
4. Enter translated text for any language you want to support. If a field uses dynamic tokens (e.g., `{percent}` or `{remaining}`), be sure to include those as it is in your translated text.
5. The storefront will automatically render the correct language based on the active buyer's locale.

## How to Think About Layout

Use this simple structure for most bundle pages:

1. Add a heading or short intro at the top.
2. Place the bundle selection area where customers can start quickly.
3. Keep the cart or selected-items area visible.
4. Add discount messaging near the selection area.
5. Use images, video, or extra text only when they help customers decide.

Containers are the main layout tool. Use them to create:

* A full-width section
* A two-column desktop layout
* A stacked mobile layout
* A grid for grouped content
* A sticky side cart area

## Design Best Practices

Use these rules to make the bundle feel trustworthy and easy to buy:

* Match your store's colors and typography.
* Keep step names short and action-focused.
* Use high-quality product images.
* Make the add-to-cart button easy to find.
* Put the most important buying action high on the page.
* Keep mobile layouts clean and compact.
* Avoid too many decorative sections before the customer can start choosing.
* Preview after large layout changes, especially when moving cart or discount blocks.

## Template Changes

Changing the selected template can reset the bundle design to that template's default layout. Before switching templates, make sure you are comfortable rebuilding any custom design changes.

## Mobile Review

Always preview the bundle on mobile before launch. Many customers will build bundles from their phone, so check:

* Product cards are easy to tap.
* Step navigation is visible.
* Variant selectors are usable.
* The selected-items cart does not hide important content.
* Text is readable without zooming.

## Design Launch Checklist

Before publishing, confirm:

* The bundle looks native to your store.
* Customers can understand the first action immediately.
* The selected-products/cart area is visible.
* Discount messaging is clear.
* The mobile experience is easy to complete.


# Publish on Your Storefront

Publish Elite Bundle Builder bundles on Shopify using the core app embed, optional app blocks, product-page display, dedicated bundle pages, preview links, and active or unlisted statuses.

This guide explains how to make a bundle visible to customers.

## Required: Theme Integration

Bundles need the core app embed to render on the storefront and run cart protection.

### Required: Enable the Core Embed

All bundle operations use the **Elite Embed Core** app embed.

1. In Shopify, go to **Online Store > Themes**.
2. Click **Customize** on your current theme.
3. Open **App embeds**.
4. Turn on **Elite Embed Core**.
5. Click **Save**.

You only need to enable this once per theme. After it is on, BYOB and MOB bundles can render and the bundle cart protections can run.

### Optional: Multi-Option Bundle Block

MOB bundles can also use the **Multi-Option Bundle** app block when you want to place the selector in a specific area of a product template.

1. In Shopify, go to **Online Store > Themes**.
2. Click **Customize** on your current theme.
3. Navigate to a **product template**.
4. In the left sidebar, click **Add section** or find the app block area.
5. Add the **Multi-Option Bundle** app block.
6. Click **Save**.

If you do not add this block, the core embed still handles the bundle experience. Add the block only when you need more control over placement.

## Choose How Bundles Display

Open **Settings > Bundle display** in the app.

You can choose:

| Display mode   | What customers see                                          | Recommended for                                           |
| -------------- | ----------------------------------------------------------- | --------------------------------------------------------- |
| Product page   | The bundle builder replaces the normal product page content | Most stores                                               |
| Dedicated page | The bundle opens on a standalone bundle page                | Stores that want a separate landing-page style experience |

Product page mode is recommended for most merchants because it keeps the bundle tied to the Shopify product page.

## Set Bundle Status

Open the bundle and choose the status:

* **Draft**: hidden from customers.
* **Unlisted**: visible by direct link for testing.
* **Active**: published for customers.

Use **Unlisted** first, test the full buying flow, then switch to **Active**.

## Preview the Bundle

From the dashboard, use the view action for a bundle. This opens the storefront preview or product URL when available.

Test these actions:

1. Open the bundle page.
2. Select products or options.
3. Confirm required steps behave correctly.
4. Add the bundle to cart.
5. Continue to checkout.
6. Confirm discounts apply as expected.

## Add the Bundle to Store Navigation

After launch, help customers find the bundle:

* Add the bundle link to your main menu.
* Feature it on the homepage.
* Add it to a collection page.
* Use it in email or SMS campaigns.
* Link it from product pages that pair well with the bundle.

## Publishing Checklist

Your bundle is ready when:

* The Elite Embed Core app embed is enabled.
* Bundle status is **Active** or **Unlisted**.
* Storefront preview loads correctly.
* Products, variants, and images are correct.
* Add to cart works.
* Checkout discounts are correct.


# Manage Bundles

Manage Shopify bundles in Elite Bundle Builder, including editing, duplicating, deleting, previewing, changing status, and maintaining active bundle offers.

The app dashboard is where you review, edit, duplicate, preview, analyze, and delete bundles.

## Bundle Dashboard

The dashboard shows:

* Total bundles
* Live bundles
* Draft bundles
* Bundle name
* Number of steps or options
* Last updated date
* Status
* Available actions

Use search and sorting to find bundles faster.

## Edit a Bundle

To edit:

1. Open the app dashboard.
2. Click the bundle name or edit action.
3. Update products, steps, status, discounts, or design.
4. Save changes.
5. Preview the bundle again.

Changes can affect the live storefront, so test important updates before promoting them.

## Duplicate a Bundle

Duplicate a bundle when you want to create a similar offer without starting over.

Good uses:

* Seasonal versions
* Region-specific bundles
* A/B testing different discount tiers
* Similar bundles for different collections

After duplicating, rename the new bundle and review every step before publishing.

## Delete a Bundle

Deleting a bundle removes it from the app and deletes its associated Shopify bundle product.

Before deleting:

* Confirm the bundle is not linked in navigation or campaigns.
* Confirm it is not part of an active promotion.
* Consider setting it to **Draft** first if you are unsure.

Deletion cannot be undone from the app.

## Update Bundle Status

Use statuses to control visibility:

* Use **Draft** while building.
* Use **Unlisted** for testing or private links.
* Use **Active** when ready for customers.

## Ongoing Maintenance Checklist

Review active bundles regularly:

* Remove out-of-stock or discontinued products.
* Check collection-based steps after major catalog changes.
* Confirm discounts still protect your margin.
* Review analytics after campaigns.
* Preview bundles after theme changes.


# Read Bundle Analytics

Understand Shopify bundle analytics for views, orders, revenue, conversion rate, average order value, and top-performing bundles in Elite Bundle Builder.

Bundle analytics help you understand which bundles attract attention, which ones turn shoppers into buyers, and where you should improve the offer, design, or promotion.

The analytics dashboard focuses on recent performance over the last 30 days.

## Where to Find Analytics

You can open analytics in two ways:

1. From the app dashboard, click the analytics action on a specific bundle.
2. Open the main **Analytics** page to review store-wide bundle performance.

Use the store-wide analytics page when you want to compare bundles. Use a bundle-specific analytics page when you want to improve one bundle.

## Dashboard Sections

The analytics dashboard includes:

* Views
* Orders
* Revenue
* Conversion
* Views over time
* Orders over time
* Revenue over time
* Average order value
* Top bundles by revenue
* Top bundles by conversion rate

Top bundle charts appear on the overall analytics page.

## What Each Metric Means

| Metric              | What it means                                | Why it matters                           |
| ------------------- | -------------------------------------------- | ---------------------------------------- |
| Views               | How many times customers viewed bundle pages | Shows demand and traffic                 |
| Orders              | How many orders included a bundle            | Shows buying activity                    |
| Revenue             | Sales attributed to bundle purchases         | Shows financial impact                   |
| Conversion          | Orders divided by views                      | Shows how well traffic turns into buyers |
| Average order value | Revenue divided by bundle orders             | Shows how valuable each bundle order is  |

## How to Read the Trends

Use the trend charts to spot changes over time.

Look for:

* A views spike after a campaign, homepage feature, or navigation change
* Orders increasing after a discount or design improvement
* Revenue increasing when customers choose higher-value products
* Conversion dropping after adding too many steps or options
* Flat views when the bundle is not promoted enough

Do not judge a bundle from one day of data. Look for patterns across several days, especially after making changes.

## Store-Wide Analytics

Use store-wide analytics to decide where to focus.

Helpful questions:

* Which bundle creates the most revenue?
* Which bundle has the strongest conversion rate?
* Which bundle gets traffic but few orders?
* Which bundle deserves more promotion?
* Which bundle should be redesigned or simplified?

If a bundle has high revenue and high conversion, promote it more. If a bundle has high views but low conversion, improve the offer or page experience.

## Bundle-Specific Analytics

Use bundle-specific analytics when improving one bundle.

Review:

* Whether views are increasing or decreasing
* Whether orders follow views
* Whether revenue is growing
* Whether conversion is healthy after design or discount changes
* Whether average order value matches your goal

## What to Do Based on the Data

### High Views, Low Orders

* Simplify the number of steps.
* Improve product images.
* Make the first step clearer.
* Add or improve tiered discounts.
* Check that the add-to-cart button is easy to find.
* Review the mobile experience.
* Make sure required steps are not too strict.

### Low Views, Good Conversion

* Add it to navigation.
* Feature it on the homepage.
* Promote it in email campaigns.
* Link it from related product pages.
* Use a clearer bundle name.
* Add the bundle to seasonal or campaign landing pages.

### Orders Are Good, Revenue Is Low

* Add higher-value products to the bundle.
* Create discount tiers that reward larger selections.
* Increase maximum quantities where it makes sense.
* Promote premium products inside the bundle.
* Review whether the first discount tier is too generous.

### Revenue Is Good, Conversion Is Low

* Keep the offer, but reduce friction.
* Move the bundle selection area higher on the page.
* Shorten intro text.
* Make discount messaging clearer.
* Test a simpler design template.

## After You Make a Change

When you edit a bundle, give the analytics time to show a pattern.

Good changes to track:

* New discount tiers
* A redesigned bundle page
* Fewer or clearer steps
* Better product images
* A homepage or navigation feature
* Email, SMS, or ad traffic

Compare the days before and after the change. Look for movement in views, orders, conversion, and revenue together.

## When Analytics Look Empty or Low

If analytics look lower than expected:

* Confirm the bundle is **Active** or **Unlisted** and has been viewed on the storefront.
* Confirm the Elite Embed Core app embed is enabled on the live theme.
* Confirm customers are visiting the bundle page, not only normal product pages.
* Confirm orders were placed through the bundle experience.
* Check the settings page for setup issues.
* Allow some time for new activity to appear.

## Privacy Note

Analytics are designed to help you understand bundle performance. Customer details may appear only when Shopify makes that context available. Use analytics responsibly and follow your store's privacy commitments.


# Troubleshooting

Troubleshoot Shopify bundle display, products, add to cart, discounts, theme embed, analytics, and setup issues in Elite Bundle Builder.

Use this guide when something does not look right.

## Bundle Is Not Showing on the Storefront

Check:

1. **Theme integration is set up**:
   * Ensure **Elite Embed Core** is enabled in **Online Store > Themes > Customize > App embeds**.
   * For **Multi-Option Bundle**, add the **Multi-Option Bundle** app block only if you need a specific product-page placement.
2. The bundle status is **Active** or **Unlisted**.
3. You are viewing the correct bundle product page or dedicated bundle page.
4. The theme was saved after enabling Elite Embed Core.

If the settings page shows a failed setup service, contact support.

## Bundle Opens but Products Are Missing

Check:

* Each step has products or a collection selected.
* Products are active in Shopify.
* Products have available variants.
* Collection-based steps contain products.
* Products have images if the design expects images.

## Customers Cannot Add the Bundle to Cart

Check:

* Required steps have selected products.
* Quantity limits are not impossible to satisfy.
* Products and variants are in stock.
* The bundle product is not in draft.
* The storefront preview works on both desktop and mobile.

## Discounts Are Not Applying

Check:

1. Discount tiers are configured.
2. The cart qualifies for a tier.
3. The bundle was added through the bundle page.
4. The app settings page does not show a failed discount service.
5. You tested in checkout, not only on the product page.

Contact support if checkout still does not show the discount.

## Bundle Looks Wrong After Changing Template

Changing templates can reset the design. Reopen the design editor and review:

* Layout
* Colors
* Product grid
* Button styles
* Discount display
* Mobile view

## Theme Changes Broke Bundle

After changing or publishing a theme:

1. Reopen the theme editor.
2. Confirm Elite Embed Core is enabled on the new live theme.
3. Save the theme.
4. Preview an active or unlisted bundle.

Theme integrations are theme-specific, so a new live theme may need to be set up again.

## Analytics Are Missing or Delayed

Check:

* The bundle has had real storefront traffic.
* The bundle page was loaded after the app setup completed.
* The settings page does not show a failed web pixel service.
* You are viewing the correct date range or bundle.

Some analytics may take time to appear.

## Still Need Help

Open the app and click **Support** or **Chat now**. Include:

* Store domain
* Bundle name
* Bundle URL
* What you expected
* What happened instead
* Screenshots, if available


# FAQ

Frequently asked questions about Elite Bundle Builder, Shopify core app embeds, discounts, inventory behavior, previews, design changes, and support.

## Does the app work with my Shopify theme?

Elite Bundle Builder is designed for Shopify Online Store 2.0 themes. Enable the same core embed for all bundle types:

* Enable the **Elite Embed Core** app embed in **Online Store > Themes > Customize > App embeds**.
* For **Multi-Option Bundle**, the **Multi-Option Bundle** app block is optional and only needed for specific product-page placement.

## Do I need to edit theme code?

No. Most setup is done through the app and Shopify theme editor:

* **Elite Embed Core**: Required app embed enabled once in theme App embeds for all bundle operations.
* **Multi-Option Bundle block**: Optional section block for placing MOB selectors in a specific product-template area.

You don't need to edit theme code manually.

## What's the difference between the core embed and app block?

* **Core embed**: Required for all bundle types. Enabled globally in theme App embeds.
* **App block**: Optional for MOB bundles. Added to a product template when you want to control the selector's exact placement.

## Can I create multiple bundles?

Yes. You can create multiple bundles, and each bundle can have its own products, steps, design, status, and discount settings.

## What is the difference between Active, Draft, and Unlisted?

**Draft** bundles are hidden from customers. **Unlisted** bundles are available by direct link and are useful for testing. **Active** bundles are published for customers.

## How do customers get the discount?

Discounts apply automatically at checkout when the customer's bundle qualifies for a configured tier. Customers do not need to enter a discount code.

## Can I use collections instead of manually selecting products?

Yes. Collection-based steps are useful when you want products to update automatically as your catalog changes.

## What happens if a product goes out of stock?

If a product or variant goes out of stock, it is shown as out of stock in the bundle and customers cannot buy that item.

For Multi-Option Bundles, unavailable products or variants are also blocked from purchase. If every product or variant in one option becomes unavailable, the whole bundle becomes unavailable because the customer can no longer complete the required bundle selection.

Review active bundles regularly, especially after inventory changes.

## Can I preview a bundle before making it public?

Yes. Set the bundle to **Unlisted**, use the preview or view action, and test the direct link before switching the status to **Active**.

## Why did my design change after switching templates?

Changing templates can reset the bundle design to the selected template's default layout. Review and save the design again after switching templates.

## How do I get support?

Open the app and click **Support** or **Chat now**. Typical response time is around 10 minutes during IST business hours, with a maximum wait of 1 business day.


# Overview

The Elite Bundle Builder SDK lets developers build fully custom storefront UIs for bundles, without being locked into the default templates.

When the SDK is enabled, the app exposes a global `window.eliteBundle.sdk` object on every bundle product page. It gives you live access to bundle data, cart state, and actions — so you can render your own product cards, step indicators, cart drawers, price calculators, or anything else, and hook our add-to-cart and checkout logic from it.

## What you can build

* A completely custom BYOB (Build Your Own Bundle) UI with your own product cards, step tabs, and side cart
* A custom MOB (Multi-Option Bundle) picker with your own swatches, dropdowns, or variant selectors
* A headless bundle experience that fits your theme's design system without any Puck component overrides

## How it works

The app injects `window.eliteBundle` on every bundle product page before your scripts run. It contains:

* **`app`** — shop-level globals: domain, money format, currency, access token, feature flags
* **`byob`** — raw BYOB bundle data from Liquid (template, config, product/variant IDs)
* **`mob`** — raw MOB bundle data from Liquid
* **`sdk.byob`** — live BYOB React state and actions, updated on every render
* **`sdk.mob`** — live MOB React state and actions, updated on every render

The default Puck-rendered UI still works normally. The SDK is additive — you can use parts of it (e.g. just `addToCart`) or replace the entire UI.

## Prerequisites

* Elite Bundle Builder installed and set up on a Shopify store
* SDK access enabled in **App → Settings → Developer SDK** (requires a developer email)
* Basic JavaScript or TypeScript knowledge

## Documentation

* [Getting Started](/developer-docs/getting-started)
* [Global Object Reference](/reference/globals)
* [BYOB SDK Reference](/reference/byob)
* [MOB SDK Reference](/reference/mob)
* [Events Reference](/reference/events)
* [Guide: Custom BYOB UI](/guides/custom-byob-ui)
* [Guide: Custom MOB UI](/guides/custom-mob-ui)
* [Guide: Using with React](/guides/react)


# Getting Started

## 1. Enable SDK access

SDK access is not enabled by default. To turn it on:

1. Open the Elite Bundle Builder app in your Shopify admin
2. Go to **Settings → Developer SDK**
3. Click **Enable SDK access** and enter your developer email
4. Click **Enable SDK access** to confirm

The SDK is immediately active. The team may also reach out with setup guidance.

## 2. Verify the global is present

Open any bundle product page in your browser, open DevTools, and run:

```js
window.eliteBundle
```

You should see an object like:

```js
{
  version: "1.0.0",
  app: {
    shopDomain: "your-store.myshopify.com",
    moneyFormat: "${{amount}}",
    currencyCode: "USD",
    currencyRate: 1,
    storefrontAccessToken: "...",
    sdkEnabled: true,
    cartTransformCreated: true,
    bundleDiscountCreated: true,
    webPixelCreated: true,
    bundleRenderMode: "product_page"
  },
  byob: { templateData: {...}, bundleConfig: {...}, shopifyVariantId: 123, productId: "789" },
  mob: null,
  sdk: {
    byob: { steps: [...], cart: [...], addToCart: fn, checkout: fn, ... },
    mob: null
  },
  on: fn,
  off: fn
}
```

`sdk` is only present when `sdkEnabled` is `true`. If it is `undefined`, SDK access has not been enabled in the app settings.

## 3. Wait for the ready event

The `sdk.byob` and `sdk.mob` objects are populated after React mounts and products are fetched from the Storefront API. Listen for the ready event before reading SDK state:

```js
document.addEventListener('elite:byob:ready', (event) => {
  const { steps, formatPrice } = window.eliteBundle.sdk.byob;
  console.log(`Bundle loaded with ${steps.length} steps`);
  steps.forEach(step => {
    console.log(step.title, step.products.map(p => p.title));
  });
});
```

For MOB:

```js
document.addEventListener('elite:mob:ready', () => {
  const { options, selectableItems, totalPrice, formatPrice } = window.eliteBundle.sdk.mob;
  console.log(`MOB loaded with ${options.length} options`);
  console.log('Total price:', formatPrice(totalPrice));
});
```

## 4. Check if the SDK is available

Before using the SDK in your theme JavaScript, guard against stores that don't have it enabled:

```js
function onSdkReady(type, callback) {
  if (!window.eliteBundle?.sdk) return; // SDK not enabled

  const eventName = type === 'mob' ? 'elite:mob:ready' : 'elite:byob:ready';
  document.addEventListener(eventName, callback);

  // Handle the case where the event already fired before your script ran
  const sdk = window.eliteBundle.sdk[type];
  if (sdk && !sdk.isLoading && !sdk.error) callback();
}

onSdkReady('byob', () => {
  // safe to use window.eliteBundle.sdk.byob here
});
```

## 5. Read static bundle config

The raw bundle config from Liquid is always available synchronously, even before React mounts:

```js
const byob = window.eliteBundle.byob;
console.log(byob.productId);       // Shopify product ID
console.log(byob.bundleConfig);    // full bundle config object
```

The `app` object is also always available:

```js
const { shopDomain, moneyFormat, storefrontAccessToken } = window.eliteBundle.app;
```


# Global Object

The top-level object injected on every bundle product page.

```ts
window.eliteBundle = {
  version: string;
  app:  EliteBundleApp;
  byob: BundleData | null;   // null on MOB-only pages
  mob:  BundleData | null;   // null on BYOB-only pages
  sdk?: {
    byob: ByobSDKState | null;
    mob:  MobSDKState  | null;
  };
  on(event: string, cb: EventListener): void;
  off(event: string, cb: EventListener): void;
}
```

`sdk` is only present when SDK access is enabled in the app settings. Always check `window.eliteBundle?.sdk` before accessing it.

***

## `app`

Shop-level globals. Set synchronously by the Liquid theme extension before any JavaScript runs. `currencyRate` is added by the bundle script after it loads.

| Field                   | Type                                 | Description                                                                                  |
| ----------------------- | ------------------------------------ | -------------------------------------------------------------------------------------------- |
| `shopDomain`            | `string`                             | Store myshopify domain, e.g. `"store.myshopify.com"`                                         |
| `moneyFormat`           | `string`                             | Shopify money format string, e.g. `"${{amount}}"`                                            |
| `currencyCode`          | `string`                             | ISO 4217 currency code, e.g. `"USD"`                                                         |
| `currencyRate`          | `number`                             | Exchange rate from shop base currency to buyer's active currency. `1.0` means same currency. |
| `storefrontAccessToken` | `string \| null`                     | Storefront API access token. Use this if you need to make Storefront API calls yourself.     |
| `cartTransformCreated`  | `boolean`                            | Whether the bundle cart transform Shopify Function is active.                                |
| `bundleDiscountCreated` | `boolean`                            | Whether the bundle discount Shopify Function is active.                                      |
| `webPixelCreated`       | `boolean`                            | Whether the analytics web pixel is active.                                                   |
| `bundleRenderMode`      | `"product_page" \| "dedicated_page"` | How the bundle renders on the storefront.                                                    |
| `sdkEnabled`            | `boolean`                            | Whether SDK access is enabled.                                                               |

```js
const { shopDomain, moneyFormat, currencyCode, storefrontAccessToken } = window.eliteBundle.app;
```

***

## `byob` / `mob`

Raw data injected by the Liquid theme extension. Available synchronously on page load, before React mounts. `null` when that bundle type is not on the current page.

| Field              | Type                       | Description                                                                       |
| ------------------ | -------------------------- | --------------------------------------------------------------------------------- |
| `templateData`     | `object`                   | Puck editor state (the visual template JSON). Not typically needed in custom UIs. |
| `bundleConfig`     | `object`                   | Full bundle configuration: steps, products, discount settings.                    |
| `shopifyVariantId` | `number \| string \| null` | Shopify variant ID of the bundle product.                                         |
| `productId`        | `string`                   | Shopify product ID. Stable identifier for this bundle.                            |

```js
const { productId, bundleConfig } = window.eliteBundle.byob;
console.log(bundleConfig.steps.length); // number of steps
```

***

## `on` / `off`

Convenience wrappers around `document.addEventListener` / `document.removeEventListener`.

```js
function handleCartChange(event) {
  const { cart, cartTotal, canCheckout } = event.detail;
  updateMyCartUI(cart);
}

window.eliteBundle.on('elite:byob:cart-change', handleCartChange);

// Later, to clean up:
window.eliteBundle.off('elite:byob:cart-change', handleCartChange);
```

See [Events Reference](/reference/events) for all available events.


# BYOB SDK

`window.eliteBundle.sdk.byob` — live BYOB state and actions, updated on every React render.

Available after the `elite:byob:ready` event fires. `null` when no BYOB bundle is on the current page.

***

## State

### `bundleId`

**`string`**

The Shopify product ID of the bundle. Stable identifier — use this to associate external state with a specific bundle.

***

### `steps`

**`ResolvedStep[]`**

All configured bundle steps, each populated with fetched Shopify product and variant data.

```ts
interface ResolvedStep {
  id: string;
  title: string;
  minQuantity: number;
  maxQuantity: number | null;
  enableSelectionLimit: boolean;
  products: ShopifyProduct[];
}

interface ShopifyProduct {
  id: string;
  title: string;
  handle: string;
  featuredImage: { url: string; altText: string | null } | null;
  variants: ProductVariant[];
}

interface ProductVariant {
  id: string;          // GID, e.g. "gid://shopify/ProductVariant/123"
  title: string;       // e.g. "Red / Small"
  price: string;       // numeric string, e.g. "29.99"
  available: boolean;
  image: { url: string; altText: string | null } | null;
  selectedOptions: { name: string; value: string }[];
}
```

**Example:**

```js
document.addEventListener('elite:byob:ready', () => {
  const { steps } = window.eliteBundle.sdk.byob;
  steps.forEach(step => {
    console.log(step.title, step.minQuantity, step.maxQuantity);
    step.products.forEach(p => {
      console.log(p.title, p.variants.map(v => v.price));
    });
  });
});
```

***

### `isLoading`

**`boolean`**

`true` while products are being fetched from the Storefront API. The `elite:byob:ready` event only fires after this becomes `false`.

***

### `error`

**`string | null`**

Non-null when product fetching failed. The `elite:byob:error` event fires at the same time.

***

### `cart`

**`CartItem[]`**

Current bundle cart contents.

```ts
interface CartItem {
  variantId: string;
  stepId: string;
  title: string;         // product title
  variantTitle: string;  // e.g. "Red / Small"
  price: number;         // numeric, in store currency
  quantity: number;
  imageUrl: string | null;
}
```

***

### `cartTotal`

**`number`**

Sum of `price × quantity` for all cart items. Rounded to 2 decimal places.

***

### `cartTotalFormatted`

**`string`**

`cartTotal` formatted using the store's money format, e.g. `"$49.99"`. Use this for display.

***

### `cartCount`

**`number`**

Total quantity of items across all cart entries.

***

### `canCheckout`

**`boolean`**

`true` when all steps with `minQuantity` constraints are satisfied. Gate your checkout button on this.

***

### `isTierMaxReached`

**`boolean`**

`true` when `cartCount` has reached the highest discount tier's quantity threshold. Useful for hiding "add more for a discount" prompts.

***

### `isCheckingOut`

**`boolean`**

`true` while the cart POST request is in flight. Use to show a loading state on your checkout button.

***

### `checkoutError`

**`string | null`**

Non-null when the last checkout attempt failed. The `elite:byob:checkout-error` event also fires.

***

### `discountConfig`

**`DiscountSettings`**

The discount configuration for this bundle. Inspect this to build a tier progress indicator.

```ts
type DiscountSettings =
  | { discountType: "none" }
  | { discountType: "percentage"; tiers: DiscountTier[] }
  | { discountType: "fixed"; tiers: DiscountTier[] };

interface DiscountTier {
  minQuantity: number;
  discountValue: number;
}
```

**Example:**

```js
const { discountConfig, cartCount } = window.eliteBundle.sdk.byob;

if (discountConfig.discountType !== "none") {
  const nextTier = discountConfig.tiers.find(t => t.minQuantity > cartCount);
  if (nextTier) {
    const remaining = nextTier.minQuantity - cartCount;
    console.log(`Add ${remaining} more for ${nextTier.discountValue}% off`);
  }
}
```

***

## Actions

### `formatPrice(amount)`

```ts
formatPrice(amount: number): string
```

Formats a numeric amount using the store's money format and active currency rate.

```js
const { formatPrice, cartTotal } = window.eliteBundle.sdk.byob;
console.log(formatPrice(cartTotal)); // "$49.99"
```

***

### `addToCart(product, variantId, stepId?)`

```ts
addToCart(product: ShopifyProduct, variantId: string, stepId?: string): void
```

Adds one unit of a variant to the bundle cart. Pass the full product object and the variant GID.

`stepId` is optional — when omitted, the system uses the variant's resolved step automatically.

```js
const { steps, addToCart } = window.eliteBundle.sdk.byob;
const step = steps[0];
const product = step.products[0];
const variant = product.variants[0];

addToCart(product, variant.id, step.id);
```

***

### `removeFromCart(variantId, stepId?)`

```ts
removeFromCart(variantId: string, stepId?: string): void
```

Removes all units of a variant from the cart. Include `stepId` if the same variant can appear in multiple steps.

***

### `updateQuantity(variantId, delta, stepId?)`

```ts
updateQuantity(variantId: string, delta: number, stepId?: string): void
```

Increments or decrements the quantity of a cart item. Use `+1` or `-1`. If quantity reaches 0, the item is removed.

```js
// Add one more
window.eliteBundle.sdk.byob.updateQuantity(variantId, 1, stepId);

// Remove one
window.eliteBundle.sdk.byob.updateQuantity(variantId, -1, stepId);
```

***

### `getItemQuantity(variantId, stepId?)`

```ts
getItemQuantity(variantId: string, stepId?: string): number
```

Returns the current quantity of a specific variant in the cart. Returns `0` if not present.

```js
const qty = window.eliteBundle.sdk.byob.getItemQuantity(variantId, stepId);
document.querySelector('.qty-badge').textContent = qty;
```

***

### `isStepFull(stepId)`

```ts
isStepFull(stepId: string): boolean
```

Returns `true` when a step has `enableSelectionLimit` enabled and its `maxQuantity` has been reached. Use to disable the add button for a step.

***

### `canAddToStep(stepId)`

```ts
canAddToStep(stepId: string): boolean
```

Returns `true` when adding another item to this step is still allowed. Inverse of `isStepFull` for steps with a limit; always `true` for unlimited steps.

```js
const { canAddToStep } = window.eliteBundle.sdk.byob;
step.products.forEach(product => {
  addBtn.disabled = !canAddToStep(step.id);
});
```

***

### `checkout(redirectTarget?)`

```ts
checkout(redirectTarget?: "checkout" | "cart" | "stay_on_page"): Promise<void>
```

Posts the bundle cart to Shopify and handles the redirect.

| `redirectTarget`       | Behavior                                |
| ---------------------- | --------------------------------------- |
| `"checkout"` (default) | Redirects to Shopify checkout           |
| `"cart"`               | Redirects to `/cart`                    |
| `"stay_on_page"`       | No redirect — stays on the product page |

```js
document.querySelector('#my-checkout-btn').addEventListener('click', () => {
  window.eliteBundle.sdk.byob.checkout('checkout');
});
```

***

## Full example

```js
document.addEventListener('elite:byob:ready', () => {
  const sdk = window.eliteBundle.sdk.byob;

  // Render steps
  sdk.steps.forEach(step => {
    const stepEl = renderStep(step);
    step.products.forEach(product => {
      product.variants.forEach(variant => {
        const btn = renderAddButton(product, variant);
        btn.disabled = !sdk.canAddToStep(step.id) || !variant.available;
        btn.addEventListener('click', () => sdk.addToCart(product, variant.id, step.id));
        stepEl.appendChild(btn);
      });
    });
    document.querySelector('#steps').appendChild(stepEl);
  });

  // Checkout button
  const checkoutBtn = document.querySelector('#checkout-btn');
  checkoutBtn.addEventListener('click', () => sdk.checkout());
});

document.addEventListener('elite:byob:cart-change', (e) => {
  const { cart, cartTotalFormatted, canCheckout } = e.detail;
  document.querySelector('#total').textContent = cartTotalFormatted;
  document.querySelector('#checkout-btn').disabled = !canCheckout;
  renderCartItems(cart);
});
```


# MOB SDK

`window.eliteBundle.sdk.mob` — live Multi-Option Bundle state and actions, updated on every React render.

Available after the `elite:mob:ready` event fires. `null` when no MOB bundle is on the current page.

***

## Option modes

MOB supports two option modes, set per-option in the admin:

| Mode                       | Behavior                                                                                                                 |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `"products"`               | Customer picks from a flat list of product/variant combos. Use `selectableItems` + `selectItem`.                         |
| `"single_product_options"` | Customer picks Shopify option values (size, color…) for a single product. Use `optionSelections` + `setOptionSelection`. |

Most options use `"products"` mode. Read `options[i].optionMode` to determine which applies.

***

## State

### `bundleId`

**`string`**

Shopify product ID of the bundle.

***

### `options`

**`BundleStep[]`**

The configured bundle options from the admin. Each option has `title`, `optionMode`, and product GID references.

***

### `optionProducts`

**`Product[][]`**

Fetched Shopify products indexed by option: `optionProducts[optionIndex][productIndex]`.

***

### `selectableItems`

**`MobSelectableItem[][]`**

Flat list of selectable product/variant combos per option (products mode only).

```ts
interface MobSelectableItem {
  key: string;           // unique key, e.g. "prod-1/var-3"
  product: Product;
  variant: ProductVariant;
  label: string;         // e.g. "Cotton Shirt - Red / Small"
  imageUrl: string;
  price: number;         // numeric
  priceFormatted: string; // e.g. "$29.99"
  available: boolean;
}
```

**Example:**

```js
document.addEventListener('elite:mob:ready', () => {
  const { options, selectableItems, selectItem } = window.eliteBundle.sdk.mob;

  options.forEach((option, i) => {
    const items = selectableItems[i] ?? [];
    items.forEach(item => {
      const card = renderProductCard(item);
      card.addEventListener('click', () => selectItem(i, item.key));
      document.querySelector(`#option-${i}`).appendChild(card);
    });
  });
});
```

***

### `selectedItemKeys`

**`Record<number, string | null>`**

Currently selected item key per option (products mode). `{ [optionIndex]: itemKey | null }`. `null` means nothing selected yet.

```js
const isSelected = window.eliteBundle.sdk.mob.selectedItemKeys[0] === item.key;
```

***

### `optionSelections`

**`Record<number, Record<string, string>>`**

Selected Shopify option values per option (single\_product\_options mode). `{ [optionIndex]: { [optionName]: selectedValue } }`.

```js
// e.g. { 0: { "Size": "M", "Color": "Red" } }
const sizeForOption0 = window.eliteBundle.sdk.mob.optionSelections[0]?.["Size"];
```

***

### `selectedVariants`

**`(ProductVariant | null)[]`**

Resolved variant for each option. `null` if that option has no valid selection yet.

***

### `totalPrice`

**`number`**

Sum of all selected variant prices. `0` when nothing is selected. Rounded to 2 decimal places.

***

### `totalPriceFormatted`

**`string`**

`totalPrice` formatted using the store's money format, e.g. `"$59.99"`. Use this for display.

***

### `discountedPrice`

**`number | null`**

Price after the bundle discount is applied. `null` when no discount is active.

***

### `discountedPriceFormatted`

**`string | null`**

`discountedPrice` formatted for display, e.g. `"$49.99"`. `null` when no discount is active.

***

### `discountPercent`

**`number`**

The active discount percentage. `0` when no discount applies.

***

### `currencyCode`

**`string`**

ISO 4217 currency code from the first fetched product, e.g. `"USD"`.

***

### `allSelected`

**`boolean`**

`true` when every option has a valid selection. Gate your add-to-cart button on this.

***

### `allSelectedVariantsAvailable`

**`boolean`**

`true` when all selected variants are available for purchase. A variant may be selected but out of stock.

***

### `isAddingToCart`

**`boolean`**

`true` while the cart POST request is in flight. Show a loading spinner on your add-to-cart button.

***

### `addToCartError`

**`string | null`**

Non-null when the last add-to-cart attempt failed.

***

### `isLoading`

**`boolean`**

`true` while products are being fetched from the Storefront API.

***

### `error`

**`string | null`**

Non-null when product fetching failed.

***

### `discountConfig`

**`DiscountSettings`**

The discount configuration for this bundle.

```ts
type DiscountSettings =
  | { discountType: "none" }
  | { discountType: "percentage"; tiers: DiscountTier[] }
  | { discountType: "fixed"; tiers: DiscountTier[] };
```

***

## Actions

### `formatPrice(amount)`

```ts
formatPrice(amount: number): string
```

Formats a monetary amount using the store's money format and active currency rate.

```js
const { formatPrice, totalPrice, discountedPrice } = window.eliteBundle.sdk.mob;
const display = discountedPrice !== null ? formatPrice(discountedPrice) : formatPrice(totalPrice);
document.querySelector('#price').textContent = display;
```

***

### `selectItem(optionIndex, itemKey)`

```ts
selectItem(optionIndex: number, itemKey: string): void
```

Selects a product/variant combo for an option (products mode). Replaces any previous selection for that option.

```js
const { selectableItems, selectItem } = window.eliteBundle.sdk.mob;

// Select the first available item in option 0
const firstAvailable = selectableItems[0].find(i => i.available);
if (firstAvailable) selectItem(0, firstAvailable.key);
```

***

### `setOptionSelection(optionIndex, optionName, value)`

```ts
setOptionSelection(optionIndex: number, optionName: string, value: string): void
```

Sets a Shopify option value for an option (single\_product\_options mode). Call once per Shopify option name.

```js
// Render a <select> for each Shopify option
product.options.forEach(shopifyOption => {
  const select = document.createElement('select');
  shopifyOption.values.forEach(val => {
    const opt = document.createElement('option');
    opt.value = val;
    opt.textContent = val;
    select.appendChild(opt);
  });
  select.addEventListener('change', e => {
    setOptionSelection(optionIndex, shopifyOption.name, e.target.value);
  });
  container.appendChild(select);
});
```

***

### `addToCart(redirectTarget?)`

```ts
addToCart(redirectTarget?: "checkout" | "cart" | "stay_on_page"): Promise<void>
```

Adds all selected variants to cart as a bundle. Only call when `allSelected` is `true`.

| `redirectTarget`       | Behavior                      |
| ---------------------- | ----------------------------- |
| `"checkout"` (default) | Redirects to Shopify checkout |
| `"cart"`               | Redirects to `/cart`          |
| `"stay_on_page"`       | No redirect                   |

```js
document.querySelector('#add-btn').addEventListener('click', () => {
  const { allSelected, isAddingToCart, addToCart } = window.eliteBundle.sdk.mob;
  if (!allSelected || isAddingToCart) return;
  addToCart('checkout');
});
```

***

## Full example

```js
document.addEventListener('elite:mob:ready', () => {
  const sdk = window.eliteBundle.sdk.mob;

  sdk.options.forEach((option, i) => {
    const container = document.querySelector(`#option-${i}`);
    container.querySelector('h2').textContent = option.title;

    const items = sdk.selectableItems[i] ?? [];
    items.forEach(item => {
      const card = document.createElement('div');
      card.className = 'product-card';
      if (!item.available) card.classList.add('unavailable');
      card.innerHTML = `
        <img src="${item.imageUrl}" alt="${item.label}" />
        <p>${item.label}</p>
        <p>${item.priceFormatted}</p>
      `;
      card.addEventListener('click', () => {
        if (!item.available) return;
        sdk.selectItem(i, item.key);
      });
      container.appendChild(card);
    });
  });

  // Price + add-to-cart
  document.querySelector('#add-btn').addEventListener('click', () => {
    sdk.addToCart();
  });
});

document.addEventListener('elite:mob:selection-change', (e) => {
  const { allSelected, totalPriceFormatted, discountedPriceFormatted } = e.detail;
  const addBtn = document.querySelector('#add-btn');
  addBtn.disabled = !allSelected;

  if (discountedPriceFormatted) {
    document.querySelector('#price').innerHTML =
      `<s>${totalPriceFormatted}</s> ${discountedPriceFormatted}`;
  } else {
    document.querySelector('#price').textContent = totalPriceFormatted;
  }

  // Highlight selected items
  document.querySelectorAll('.product-card').forEach(card => {
    card.classList.toggle('selected', card.dataset.key === e.detail.selectedItemKeys[card.dataset.optionIndex]);
  });
});
```


# Events

All events are dispatched on `document` as `CustomEvent` instances with `bubbles: true`. Listen with `document.addEventListener` or the convenience wrappers `window.eliteBundle.on` / `window.eliteBundle.off`.

```js
window.eliteBundle.on('elite:byob:ready', (event) => {
  console.log(event.detail); // { bundleId, stepCount }
});
```

***

## BYOB Events

### `elite:byob:ready`

Fired once after BYOB finishes loading products from the Storefront API and React has mounted. This is the safe point to start reading `window.eliteBundle.sdk.byob`.

**Detail:**

```ts
{
  bundleId: string;   // Shopify product ID
  stepCount: number;  // number of configured steps
}
```

**Example:**

```js
document.addEventListener('elite:byob:ready', (event) => {
  const { bundleId, stepCount } = event.detail;
  console.log(`Bundle ${bundleId} loaded with ${stepCount} steps`);

  const { steps } = window.eliteBundle.sdk.byob;
  renderMySteps(steps);
});
```

***

### `elite:byob:error`

Fired when BYOB fails to load (e.g. Storefront API error, network failure).

**Detail:**

```ts
{
  error: string; // human-readable error message
}
```

**Example:**

```js
document.addEventListener('elite:byob:error', (event) => {
  showErrorBanner(event.detail.error);
});
```

***

### `elite:byob:cart-change`

Fired after every cart mutation: add, remove, or quantity update. Use this to keep your custom cart UI in sync.

**Detail:**

```ts
{
  cart: CartItem[];
  cartTotal: number;
  cartTotalFormatted: string;  // e.g. "$49.99"
  cartCount: number;
  canCheckout: boolean;
}
```

**Example:**

```js
document.addEventListener('elite:byob:cart-change', (event) => {
  const { cart, cartTotalFormatted, cartCount, canCheckout } = event.detail;

  document.querySelector('#cart-count').textContent = cartCount;
  document.querySelector('#cart-total').textContent = cartTotalFormatted;
  document.querySelector('#checkout-btn').disabled = !canCheckout;

  renderCartItems(cart);
});
```

***

### `elite:byob:checkout-start`

Fired when `checkout()` is called, before the cart POST request is sent.

**Detail:**

```ts
{
  cart: CartItem[];
  cartTotal: number;
  cartCount: number;
}
```

**Example:**

```js
document.addEventListener('elite:byob:checkout-start', () => {
  document.querySelector('#checkout-btn').textContent = 'Processing…';
});
```

***

### `elite:byob:checkout-success`

Fired after the cart POST request succeeds. If `redirectTarget` is `"checkout"` or `"cart"`, the page navigates immediately after this event.

**Detail:**

```ts
{
  redirectTarget: "checkout" | "cart" | "stay_on_page";
}
```

***

### `elite:byob:checkout-error`

Fired when the cart POST request fails.

**Detail:**

```ts
{
  error: string; // human-readable error message
}
```

**Example:**

```js
document.addEventListener('elite:byob:checkout-error', (event) => {
  document.querySelector('#checkout-btn').textContent = 'Try again';
  showToast(event.detail.error);
});
```

***

## MOB Events

### `elite:mob:ready`

Fired once after MOB finishes loading products and React has mounted. Safe point to start reading `window.eliteBundle.sdk.mob`.

**Detail:**

```ts
{
  bundleId: string;    // Shopify product ID
  optionCount: number; // number of configured options
}
```

**Example:**

```js
document.addEventListener('elite:mob:ready', (event) => {
  const { optionCount } = event.detail;
  console.log(`MOB loaded with ${optionCount} options`);

  const { options, selectableItems } = window.eliteBundle.sdk.mob;
  renderMyPicker(options, selectableItems);
});
```

***

### `elite:mob:error`

Fired when MOB fails to load.

**Detail:**

```ts
{
  error: string;
}
```

***

### `elite:mob:selection-change`

Fired after any selection change: `selectItem` or `setOptionSelection`. Use this to update price displays and enable/disable the add-to-cart button.

**Detail:**

```ts
{
  selectedVariants: (ProductVariant | null)[];
  selectedItemKeys: Record<number, string | null>;
  allSelected: boolean;
  totalPrice: number;
  totalPriceFormatted: string;
  discountedPrice: number | null;
  discountedPriceFormatted: string | null;
}
```

**Example:**

```js
document.addEventListener('elite:mob:selection-change', (event) => {
  const {
    allSelected,
    totalPriceFormatted,
    discountedPriceFormatted,
    selectedItemKeys,
  } = event.detail;

  document.querySelector('#add-btn').disabled = !allSelected;

  const priceEl = document.querySelector('#price');
  if (discountedPriceFormatted) {
    priceEl.innerHTML = `<s>${totalPriceFormatted}</s> ${discountedPriceFormatted}`;
  } else {
    priceEl.textContent = totalPriceFormatted;
  }
});
```

***

### `elite:mob:cart-add-start`

Fired when `addToCart()` is called, before the cart POST request.

**Detail:**

```ts
{
  selectedVariants: (ProductVariant | null)[];
}
```

***

### `elite:mob:cart-add-success`

Fired after the cart POST request succeeds.

**Detail:**

```ts
{
  redirectTarget: "checkout" | "cart" | "stay_on_page";
}
```

***

### `elite:mob:cart-add-error`

Fired when the cart POST request fails.

**Detail:**

```ts
{
  error: string;
}
```

***

## Event timing

Both `elite:byob:ready` and `elite:mob:ready` can fire before your theme JavaScript runs, depending on load order. Always guard against this:

```js
function onBundleReady(type, callback) {
  if (!window.eliteBundle?.sdk) return; // SDK not enabled

  const eventName = type === 'mob' ? 'elite:mob:ready' : 'elite:byob:ready';
  document.addEventListener(eventName, callback);

  // Handle the case where ready already fired before this script ran
  const sdk = window.eliteBundle.sdk[type];
  if (sdk && !sdk.isLoading && !sdk.error) callback();
}

onBundleReady('byob', () => {
  // safe to use window.eliteBundle.sdk.byob here
});
```

See [Getting Started](/developer-docs/getting-started) for the full guard pattern.


# Custom BYOB UI

This guide walks through building a fully custom Build Your Own Bundle UI from scratch, using the Elite Bundle Builder SDK for data and actions while rendering your own HTML.

The default Puck-rendered template continues to exist — you can replace individual parts or the whole thing.

***

## Prerequisites

* SDK access enabled in **App → Settings → Developer SDK**
* A bundle product set up with at least one step
* Access to your Shopify theme files

***

## 1. Add your HTML scaffold

In your theme, add the container elements where your custom bundle UI will render. Place these on the bundle product page — either in the product template or a theme section.

```html
<!-- _theme/sections/custom-byob.liquid or similar -->
<div id="my-bundle-root" style="display: none;">
  <div id="my-steps"></div>
  <div id="my-cart-drawer">
    <ul id="my-cart-items"></ul>
    <p>Total: <span id="my-cart-total">$0.00</span></p>
    <button id="my-checkout-btn" disabled>Checkout</button>
  </div>
</div>
<div id="my-bundle-loading">Loading bundle…</div>
<div id="my-bundle-error" style="display: none;"></div>
```

***

## 2. Wait for the SDK to be ready

The SDK populates `window.eliteBundle.sdk.byob` after React mounts and products load. Listen for the ready event before reading any state.

```js
function onByobReady(callback) {
  if (!window.eliteBundle?.sdk) return; // SDK not enabled, abort

  document.addEventListener('elite:byob:ready', callback);

  // Handle the case where ready fired before this script loaded
  const sdk = window.eliteBundle.sdk.byob;
  if (sdk && !sdk.isLoading && !sdk.error) callback();
}

onByobReady(initMyBundleUI);
```

***

## 3. Render steps and products

```js
function initMyBundleUI() {
  const sdk = window.eliteBundle.sdk.byob;

  document.querySelector('#my-bundle-loading').style.display = 'none';
  document.querySelector('#my-bundle-root').style.display = '';

  const stepsContainer = document.querySelector('#my-steps');

  sdk.steps.forEach(step => {
    const stepEl = document.createElement('div');
    stepEl.className = 'bundle-step';
    stepEl.innerHTML = `<h2>${step.title}</h2>`;

    // Add a products grid
    const grid = document.createElement('div');
    grid.className = 'products-grid';

    step.products.forEach(product => {
      product.variants.forEach(variant => {
        const card = document.createElement('div');
        card.className = 'product-card';
        card.dataset.variantId = variant.id;
        card.dataset.stepId = step.id;

        const image = product.featuredImage?.url ?? '';
        const price = sdk.formatPrice(parseFloat(variant.price));

        card.innerHTML = `
          <img src="${image}" alt="${product.title}" />
          <p class="product-title">${product.title}</p>
          ${variant.title !== 'Default Title' ? `<p class="variant-title">${variant.title}</p>` : ''}
          <p class="price">${price}</p>
          <div class="qty-controls" style="display: none;">
            <button class="qty-dec">−</button>
            <span class="qty-display">0</span>
            <button class="qty-inc">+</button>
          </div>
          <button class="add-btn" ${!variant.available ? 'disabled' : ''}>
            ${variant.available ? 'Add' : 'Sold out'}
          </button>
        `;

        // Add to cart
        card.querySelector('.add-btn').addEventListener('click', () => {
          sdk.addToCart(product, variant.id, step.id);
        });

        // Quantity controls
        card.querySelector('.qty-inc').addEventListener('click', () => {
          sdk.updateQuantity(variant.id, 1, step.id);
        });
        card.querySelector('.qty-dec').addEventListener('click', () => {
          sdk.updateQuantity(variant.id, -1, step.id);
        });

        grid.appendChild(card);
      });
    });

    stepEl.appendChild(grid);
    stepsContainer.appendChild(stepEl);
  });
}
```

***

## 4. Keep the cart in sync

Listen for `elite:byob:cart-change` to update your UI after every cart mutation.

```js
document.addEventListener('elite:byob:cart-change', (event) => {
  const { cart, cartTotalFormatted, cartCount, canCheckout } = event.detail;
  const sdk = window.eliteBundle.sdk.byob;

  // Update total and checkout button
  document.querySelector('#my-cart-total').textContent = cartTotalFormatted;
  document.querySelector('#my-checkout-btn').disabled = !canCheckout;

  // Update cart items list
  const itemsList = document.querySelector('#my-cart-items');
  itemsList.innerHTML = '';
  cart.forEach(item => {
    const li = document.createElement('li');
    li.textContent = `${item.title} × ${item.quantity} — ${sdk.formatPrice(item.price * item.quantity)}`;
    itemsList.appendChild(li);
  });

  // Update per-card quantity displays and step limits
  document.querySelectorAll('.product-card').forEach(card => {
    const variantId = card.dataset.variantId;
    const stepId = card.dataset.stepId;
    const qty = sdk.getItemQuantity(variantId, stepId);

    const qtyControls = card.querySelector('.qty-controls');
    const addBtn = card.querySelector('.add-btn');
    const qtyDisplay = card.querySelector('.qty-display');

    qtyControls.style.display = qty > 0 ? '' : 'none';
    qtyDisplay.textContent = qty;

    const stepFull = sdk.isStepFull(stepId);
    addBtn.disabled = stepFull && qty === 0;
  });
});
```

***

## 5. Handle checkout

```js
const checkoutBtn = document.querySelector('#my-checkout-btn');

checkoutBtn.addEventListener('click', () => {
  window.eliteBundle.sdk.byob.checkout('checkout');
});

// Loading state while checkout is in flight
document.addEventListener('elite:byob:checkout-start', () => {
  checkoutBtn.textContent = 'Processing…';
  checkoutBtn.disabled = true;
});

document.addEventListener('elite:byob:checkout-error', (event) => {
  checkoutBtn.textContent = 'Checkout';
  checkoutBtn.disabled = false;
  alert('Checkout failed: ' + event.detail.error);
});
```

***

## 6. Show a discount tier indicator (optional)

```js
function renderTierProgress() {
  const { discountConfig, cartCount, isTierMaxReached } = window.eliteBundle.sdk.byob;

  if (discountConfig.discountType === 'none') return;

  const tiers = discountConfig.tiers;
  const nextTier = tiers.find(t => t.minQuantity > cartCount);

  const progressEl = document.querySelector('#discount-progress');

  if (isTierMaxReached || !nextTier) {
    progressEl.textContent = `Maximum discount applied!`;
    return;
  }

  const remaining = nextTier.minQuantity - cartCount;
  progressEl.textContent =
    `Add ${remaining} more item${remaining !== 1 ? 's' : ''} for ${nextTier.discountValue}% off`;
}

document.addEventListener('elite:byob:cart-change', renderTierProgress);
```

***

## 7. Handle errors

```js
document.addEventListener('elite:byob:error', (event) => {
  document.querySelector('#my-bundle-loading').style.display = 'none';
  const errorEl = document.querySelector('#my-bundle-error');
  errorEl.textContent = 'Failed to load bundle: ' + event.detail.error;
  errorEl.style.display = '';
});
```

***

## Hiding the default UI

If you want to replace (not augment) the default Puck-rendered template, hide it with CSS. The default bundle renders inside a `[data-byob-bundle]` element.

```css
/* In your theme CSS */
[data-byob-bundle] {
  display: none !important;
}
```

Or hide it in JavaScript after the ready event, so there's no flash:

```js
document.addEventListener('elite:byob:ready', () => {
  const defaultUI = document.querySelector('[data-byob-bundle]');
  if (defaultUI) defaultUI.style.display = 'none';
});
```


# Custom MOB UI

This guide walks through building a fully custom Multi-Option Bundle UI using the Elite Bundle Builder SDK.

MOB bundles present a fixed set of options (e.g. "Choose a shirt", "Choose a hat") where the customer selects one item per option. The SDK handles all cart logic — you just render the picker UI and call `selectItem` when the customer makes a choice.

***

## Prerequisites

* SDK access enabled in **App → Settings → Developer SDK**
* A MOB bundle product set up with at least one option
* Access to your Shopify theme files

***

## 1. Add your HTML scaffold

```html
<div id="my-mob-root" style="display: none;">
  <div id="my-mob-options"></div>

  <div id="my-mob-summary">
    <p id="my-mob-price"></p>
    <button id="my-mob-add-btn" disabled>Add to cart</button>
    <p id="my-mob-error" style="display: none; color: red;"></p>
  </div>
</div>
<div id="my-mob-loading">Loading bundle…</div>
```

***

## 2. Wait for the SDK to be ready

```js
function onMobReady(callback) {
  if (!window.eliteBundle?.sdk) return; // SDK not enabled

  document.addEventListener('elite:mob:ready', callback);

  // Handle the case where ready fired before this script loaded
  const sdk = window.eliteBundle.sdk.mob;
  if (sdk && !sdk.isLoading && !sdk.error) callback();
}

onMobReady(initMyMobUI);
```

***

## 3. Render options (products mode)

Most MOB options use `"products"` mode — a flat list of product/variant combos the customer picks from.

```js
function initMyMobUI() {
  const sdk = window.eliteBundle.sdk.mob;

  document.querySelector('#my-mob-loading').style.display = 'none';
  document.querySelector('#my-mob-root').style.display = '';

  const optionsContainer = document.querySelector('#my-mob-options');

  sdk.options.forEach((option, i) => {
    const optionEl = document.createElement('div');
    optionEl.className = 'mob-option';
    optionEl.dataset.optionIndex = i;
    optionEl.innerHTML = `<h3>${option.title}</h3>`;

    const items = sdk.selectableItems[i] ?? [];
    items.forEach(item => {
      const card = createItemCard(item, i);
      optionEl.appendChild(card);
    });

    optionsContainer.appendChild(optionEl);
  });

  // Checkout button
  document.querySelector('#my-mob-add-btn').addEventListener('click', () => {
    sdk.addToCart('checkout');
  });
}

function createItemCard(item, optionIndex) {
  const card = document.createElement('div');
  card.className = 'mob-item-card';
  card.dataset.itemKey = item.key;
  card.dataset.optionIndex = optionIndex;

  if (!item.available) {
    card.classList.add('unavailable');
  }

  card.innerHTML = `
    <img src="${item.imageUrl}" alt="${item.label}" />
    <p>${item.label}</p>
    <p>${item.priceFormatted}</p>
  `;

  card.addEventListener('click', () => {
    if (!item.available) return;
    window.eliteBundle.sdk.mob.selectItem(optionIndex, item.key);
  });

  return card;
}
```

***

## 4. Handle single\_product\_options mode

When an option uses `"single_product_options"` mode, render a set of dropdowns — one per Shopify option (Size, Color, etc.).

```js
function renderSingleProductOption(option, optionIndex, product) {
  const sdk = window.eliteBundle.sdk.mob;
  const container = document.createElement('div');
  container.className = 'mob-option';
  container.innerHTML = `<h3>${option.title}</h3>`;

  product.options.forEach(shopifyOption => {
    const label = document.createElement('label');
    label.textContent = shopifyOption.name;

    const select = document.createElement('select');
    shopifyOption.values.forEach(val => {
      const opt = document.createElement('option');
      opt.value = val;
      opt.textContent = val;
      select.appendChild(opt);
    });

    select.addEventListener('change', (e) => {
      sdk.setOptionSelection(optionIndex, shopifyOption.name, e.target.value);
    });

    container.appendChild(label);
    container.appendChild(select);
  });

  return container;
}
```

To determine which mode to use for each option, check `option.optionMode`:

```js
sdk.options.forEach((option, i) => {
  if (option.optionMode === 'single_product_options') {
    const product = sdk.optionProducts[i]?.[0];
    if (product) {
      optionsContainer.appendChild(renderSingleProductOption(option, i, product));
    }
  } else {
    // default "products" mode — render flat item list
    optionsContainer.appendChild(renderProductsOption(option, i));
  }
});
```

***

## 5. Keep the UI in sync with selections

Listen for `elite:mob:selection-change` to update selected state and prices after every selection.

```js
document.addEventListener('elite:mob:selection-change', (event) => {
  const {
    selectedItemKeys,
    allSelected,
    totalPriceFormatted,
    discountedPriceFormatted,
  } = event.detail;

  // Enable/disable add-to-cart button
  document.querySelector('#my-mob-add-btn').disabled = !allSelected;

  // Update price display
  const priceEl = document.querySelector('#my-mob-price');
  if (discountedPriceFormatted) {
    priceEl.innerHTML = `<s>${totalPriceFormatted}</s> <strong>${discountedPriceFormatted}</strong>`;
  } else {
    priceEl.textContent = totalPriceFormatted;
  }

  // Highlight selected cards
  document.querySelectorAll('.mob-item-card').forEach(card => {
    const optionIndex = Number(card.dataset.optionIndex);
    const key = card.dataset.itemKey;
    card.classList.toggle('selected', selectedItemKeys[optionIndex] === key);
  });
});
```

***

## 6. Handle add-to-cart states

```js
// Loading
document.addEventListener('elite:mob:cart-add-start', () => {
  const btn = document.querySelector('#my-mob-add-btn');
  btn.textContent = 'Adding…';
  btn.disabled = true;
});

// Error
document.addEventListener('elite:mob:cart-add-error', (event) => {
  const btn = document.querySelector('#my-mob-add-btn');
  btn.textContent = 'Add to cart';
  btn.disabled = false;

  const errorEl = document.querySelector('#my-mob-error');
  errorEl.textContent = event.detail.error;
  errorEl.style.display = '';
});

// Success (page navigates if redirectTarget !== "stay_on_page")
document.addEventListener('elite:mob:cart-add-success', () => {
  document.querySelector('#my-mob-add-btn').textContent = 'Added!';
});
```

***

## 7. Show discount info (optional)

```js
document.addEventListener('elite:mob:selection-change', (event) => {
  const { discountedPrice, discountedPriceFormatted } = event.detail;
  const sdk = window.eliteBundle.sdk.mob;

  const discountEl = document.querySelector('#discount-badge');
  if (!discountEl) return;

  if (discountedPrice !== null && sdk.discountPercent > 0) {
    discountEl.textContent = `${sdk.discountPercent}% bundle discount applied`;
    discountEl.style.display = '';
  } else {
    discountEl.style.display = 'none';
  }
});
```

***

## 8. Handle load errors

```js
document.addEventListener('elite:mob:error', (event) => {
  document.querySelector('#my-mob-loading').textContent =
    'Failed to load bundle: ' + event.detail.error;
});
```

***

## Hiding the default UI

The default MOB template renders inside a `[data-mob-bundle]` element. Hide it with CSS if you're replacing it entirely:

```css
[data-mob-bundle] {
  display: none !important;
}
```


# Using with React

The SDK is framework-agnostic — it exposes plain JavaScript objects and events. This guide shows patterns for consuming the SDK inside a React app embedded in a Shopify theme.

***

## Setup

Install your React app however you like (a `<script type="module">` tag, Vite, or a bundled file). It just needs to run on the same page as the bundle.

The SDK is always available at `window.eliteBundle` — no install or import required. You access it from React via refs and effects, the same as any other global.

***

## 1. Wait for ready and sync to state

Use a `useEffect` to subscribe to the ready event, then mirror the SDK state into React.

```tsx
import { useState, useEffect, useCallback } from 'react';

interface ByobSDK {
  steps: any[];
  cart: any[];
  cartTotalFormatted: string;
  canCheckout: boolean;
  addToCart: (product: any, variantId: string, stepId?: string) => void;
  checkout: (target?: string) => Promise<void>;
  formatPrice: (amount: number) => string;
}

function useBundleSDK() {
  const [sdk, setSdk] = useState<ByobSDK | null>(null);
  const [isReady, setIsReady] = useState(false);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    if (!window.eliteBundle?.sdk) return; // SDK not enabled

    function onReady() {
      setSdk(window.eliteBundle.sdk.byob);
      setIsReady(true);
    }

    function onError(e: CustomEvent<{ error: string }>) {
      setError(e.detail.error);
    }

    document.addEventListener('elite:byob:ready', onReady);
    document.addEventListener('elite:byob:error', onError as EventListener);

    // Handle ready already fired
    const current = window.eliteBundle.sdk.byob;
    if (current && !current.isLoading && !current.error) onReady();

    return () => {
      document.removeEventListener('elite:byob:ready', onReady);
      document.removeEventListener('elite:byob:error', onError as EventListener);
    };
  }, []);

  return { sdk, isReady, error };
}
```

***

## 2. Sync cart state from SDK events

The SDK state updates on every React render inside the bundle iframe, but your React app is a separate tree. Use events to get live cart updates.

```tsx
function useByobCart() {
  const [cart, setCart] = useState([]);
  const [cartTotal, setCartTotal] = useState('$0.00');
  const [canCheckout, setCanCheckout] = useState(false);

  useEffect(() => {
    function onCartChange(e: CustomEvent) {
      const { cart, cartTotalFormatted, canCheckout } = e.detail;
      setCart(cart);
      setCartTotal(cartTotalFormatted);
      setCanCheckout(canCheckout);
    }

    document.addEventListener('elite:byob:cart-change', onCartChange as EventListener);
    return () => {
      document.removeEventListener('elite:byob:cart-change', onCartChange as EventListener);
    };
  }, []);

  return { cart, cartTotal, canCheckout };
}
```

***

## 3. Example component

```tsx
function BundleBuilder() {
  const { sdk, isReady, error } = useBundleSDK();
  const { cart, cartTotal, canCheckout } = useByobCart();
  const [isCheckingOut, setIsCheckingOut] = useState(false);

  const handleCheckout = useCallback(async () => {
    if (!sdk || !canCheckout) return;
    setIsCheckingOut(true);
    try {
      await sdk.checkout('checkout');
    } finally {
      setIsCheckingOut(false);
    }
  }, [sdk, canCheckout]);

  if (error) return <p>Error: {error}</p>;
  if (!isReady || !sdk) return <p>Loading…</p>;

  return (
    <div>
      {sdk.steps.map(step => (
        <StepSection key={step.id} step={step} sdk={sdk} />
      ))}

      <CartDrawer
        cart={cart}
        cartTotal={cartTotal}
        canCheckout={canCheckout}
        isCheckingOut={isCheckingOut}
        onCheckout={handleCheckout}
      />
    </div>
  );
}
```

***

## 4. Per-card quantity state

Instead of storing quantities in React state (which would require diffing the cart array on every change), read them directly from the SDK on demand.

```tsx
function ProductCard({ product, variant, stepId }) {
  const [qty, setQty] = useState(0);

  useEffect(() => {
    function sync() {
      const sdk = window.eliteBundle?.sdk?.byob;
      if (!sdk) return;
      setQty(sdk.getItemQuantity(variant.id, stepId));
    }

    document.addEventListener('elite:byob:cart-change', sync);
    sync(); // initialize

    return () => document.removeEventListener('elite:byob:cart-change', sync);
  }, [variant.id, stepId]);

  const handleAdd = () => {
    const sdk = window.eliteBundle?.sdk?.byob;
    sdk?.addToCart(product, variant.id, stepId);
  };

  const handleInc = () => {
    window.eliteBundle?.sdk?.byob?.updateQuantity(variant.id, 1, stepId);
  };

  const handleDec = () => {
    window.eliteBundle?.sdk?.byob?.updateQuantity(variant.id, -1, stepId);
  };

  return (
    <div>
      <img src={product.featuredImage?.url} alt={product.title} />
      <p>{product.title}</p>
      {qty > 0 ? (
        <div>
          <button onClick={handleDec}>−</button>
          <span>{qty}</span>
          <button onClick={handleInc}>+</button>
        </div>
      ) : (
        <button onClick={handleAdd} disabled={!variant.available}>
          {variant.available ? 'Add' : 'Sold out'}
        </button>
      )}
    </div>
  );
}
```

***

## 5. MOB in React

Same pattern — subscribe to `elite:mob:ready`, then use `elite:mob:selection-change` for reactive updates.

```tsx
function useMobSDK() {
  const [sdk, setSdk] = useState(null);
  const [selection, setSelection] = useState({
    allSelected: false,
    totalPriceFormatted: '$0.00',
    discountedPriceFormatted: null,
  });

  useEffect(() => {
    if (!window.eliteBundle?.sdk) return;

    function onReady() {
      setSdk(window.eliteBundle.sdk.mob);
    }

    function onSelectionChange(e) {
      setSelection({
        allSelected: e.detail.allSelected,
        totalPriceFormatted: e.detail.totalPriceFormatted,
        discountedPriceFormatted: e.detail.discountedPriceFormatted,
      });
    }

    document.addEventListener('elite:mob:ready', onReady);
    document.addEventListener('elite:mob:selection-change', onSelectionChange);

    const current = window.eliteBundle.sdk.mob;
    if (current && !current.isLoading && !current.error) onReady();

    return () => {
      document.removeEventListener('elite:mob:ready', onReady);
      document.removeEventListener('elite:mob:selection-change', onSelectionChange);
    };
  }, []);

  return { sdk, selection };
}

function MobPicker() {
  const { sdk, selection } = useMobSDK();

  if (!sdk) return <p>Loading…</p>;

  return (
    <div>
      {sdk.options.map((option, i) => (
        <OptionSection
          key={i}
          option={option}
          items={sdk.selectableItems[i] ?? []}
          selectedKey={sdk.selectedItemKeys[i]}
          onSelect={(key) => sdk.selectItem(i, key)}
        />
      ))}

      <p>
        {selection.discountedPriceFormatted ? (
          <>
            <s>{selection.totalPriceFormatted}</s>{' '}
            <strong>{selection.discountedPriceFormatted}</strong>
          </>
        ) : selection.totalPriceFormatted}
      </p>

      <button
        disabled={!selection.allSelected}
        onClick={() => sdk.addToCart('checkout')}
      >
        Add bundle to cart
      </button>
    </div>
  );
}
```

***

## TypeScript

Import the SDK types from the package if you need them. Since they're not exported as an npm package, copy the relevant interfaces from [BYOB SDK Reference](/reference/byob) and [MOB SDK Reference](/reference/mob) into your own `types.ts`.

Or add a global declaration to extend the `Window` type:

```ts
// types.d.ts
declare global {
  interface Window {
    eliteBundle: {
      version: string;
      app: Record<string, unknown>;
      byob: Record<string, unknown> | null;
      mob: Record<string, unknown> | null;
      sdk?: {
        byob: any | null;
        mob: any | null;
      };
      on(event: string, cb: EventListener): void;
      off(event: string, cb: EventListener): void;
    };
  }
}
```


