> For the complete documentation index, see [llms.txt](https://docs.elitebundleapp.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.elitebundleapp.com/reference/mob.md).

# 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]);
  });
});
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.elitebundleapp.com/reference/mob.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
