> 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/byob.md).

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


---

# 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/byob.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.
