> ## Documentation Index
> Fetch the complete documentation index at: https://developers.appstle.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Dynamic pricing bundle with a custom storefront

> Offer a build-your-own bundle that applies a percentage or fixed-amount discount to the running total, using your own storefront UI and Appstle's automatic discount.

A **dynamic pricing bundle** lets customers build their own selection from an eligible set, and applies a discount to the running total — either a **percentage** (e.g. *"15% off when you bundle any 3"*) or a **fixed amount** (e.g. *"\$10 off your build-your-own box"*). Unlike a fixed pricing bundle, the total scales with what the customer picks; the discount is layered on top.

This guide covers running a dynamic pricing bundle from your own storefront UI. You use the *same* metafield, cart-line attributes, and Shopify Function that Appstle's built-in widget uses — only the UI is yours. Read the [headless overview](/bundles/headless-overview) first for the automatic-discount model.

|                         |                                               |
| ----------------------- | --------------------------------------------- |
| **`bundleType`**        | `CLASSIC_BUILD_A_BOX`                         |
| **Read from**           | `appstle_bundles.bundle_rules` shop metafield |
| **Discount applied by** | Appstle automatic discount (Shopify Function) |

## How pricing works

The rule defines a `discountType` and `discountValue`, plus optional selection gates:

| `discountType` | Discount on the eligible subtotal                    |
| -------------- | ---------------------------------------------------- |
| `PERCENTAGE`   | `subtotal × (discountValue / 100)` off               |
| `FIXED_AMOUNT` | `discountValue` off                                  |
| `NO_DISCOUNT`  | No discount (the bundle exists purely for grouping). |

The discount applies only when the selection satisfies the configured gates:

* **Item count** between `minProductCount` and `maxProductCount`.
* **Selection value** between `minOrderAmount` and `maxOrderAmount`.

So the customer-facing math is:

```
subtotal       = Σ (line price × quantity)        // eligible bundle lines
discount       = PERCENTAGE → subtotal × value/100
                 FIXED_AMOUNT → value
customer pays  = subtotal − discount               // when gates are satisfied
```

Appstle's Function evaluates this and applies the discount automatically once the lines carry the bundle attributes.

## Step 1 — Read the rule from the metafield

Read the active rules from the **`appstle_bundles.bundle_rules`** shop metafield — the Shopify-native source, with no call to Appstle's servers (see [Reading bundle configuration](/bundles/headless-overview#reading-bundle-configuration)). Filter to dynamic pricing bundles by `bundleType`.

```liquid theme={null}
{% assign bundle_rules = shop.metafields.appstle_bundles.bundle_rules.value %}
<script>window.MY_BUNDLE_RULES = {{ bundle_rules | json }};</script>
```

```javascript theme={null}
const rules = window.MY_BUNDLE_RULES || [];

const dynamicBundles = rules.filter(
  r => r.bundleType === 'CLASSIC_BUILD_A_BOX' && r.status === 'ACTIVE'
);
```

Fields you need:

| Field                                 | Use                                             |
| ------------------------------------- | ----------------------------------------------- |
| `uniqueRef`                           | Attach to every cart line as `_appstle-bb-id`.  |
| `name`                                | Display label; attach as `__appstle-bb-name`.   |
| `discountType`                        | `PERCENTAGE`, `FIXED_AMOUNT`, or `NO_DISCOUNT`. |
| `discountValue`                       | The percentage or fixed amount.                 |
| `minProductCount` / `maxProductCount` | Item-count gate.                                |
| `minOrderAmount` / `maxOrderAmount`   | Selection-value gate.                           |
| `products` / `variants`               | Eligible products and variants (JSON).          |

## Step 2 — Render your selector

Build your own selector and gate the "add to cart" action on the rule:

* Allow only eligible variants.
* Track the running item count and subtotal; enable checkout only when both gates are satisfied.
* Show the live discounted total using the [preview formula](#previewing-the-discount).

## Step 3 — Add the bundle to the cart

Add the selected variants with the [bundle attributes](/bundles/headless-overview#required-cart-line-attributes). For a dynamic pricing bundle, `_appstle_bundles_type` is `CLASSIC_BUILD_A_BOX`.

<CodeGroup>
  ```js Shopify theme (Ajax Cart API) theme={null}
  const line = (variantId, quantity, sellingPlanId = null) => ({
    id: variantId,
    quantity,
    ...(sellingPlanId ? { selling_plan: sellingPlanId } : {}),
    properties: {
      '_appstle-bb-id': bundle.uniqueRef,
      '_appstle_bundles_type': 'CLASSIC_BUILD_A_BOX',
      '__appstle-bb-name': bundle.name,
    },
  });

  await fetch(`${Shopify.routes.root}cart/add.js`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ items: selection.map(s => line(s.variantId, s.quantity)) }),
  });
  ```

  ```graphql Headless (Storefront API) theme={null}
  mutation AddDynamicBundle($cartId: ID!) {
    cartLinesAdd(
      cartId: $cartId
      lines: [
        {
          merchandiseId: "gid://shopify/ProductVariant/123"
          quantity: 2
          attributes: [
            { key: "_appstle-bb-id", value: "byo-box-ref" }
            { key: "_appstle_bundles_type", value: "CLASSIC_BUILD_A_BOX" }
            { key: "__appstle-bb-name", value: "Build Your Own Box" }
          ]
        }
      ]
    ) {
      cart { id }
      userErrors { field message }
    }
  }
  ```
</CodeGroup>

<Note>
  Dynamic pricing bundles support subscriptions. If the selection uses a subscription purchase option, pass the chosen `selling_plan` on the line (Ajax) or `sellingPlanId` on the merchandise line (Storefront API), exactly as you would for any subscription line item.
</Note>

## Step 4 — Discount applies automatically

When the lines carrying the attributes satisfy the gates, Appstle's automatic discount applies the percentage or fixed amount in cart and checkout. If the selection later drops below the gate (e.g. an item is removed), the Function stops applying the discount — your UI does not need to manage this.

## Previewing the discount

Mirror the Function's math for a live preview (cart prices are in cents):

```js theme={null}
function previewDynamicDiscount(bundle, selection) {
  const subtotalCents = selection.reduce((s, i) => s + i.priceCents * i.quantity, 0);
  const count = selection.reduce((n, i) => n + i.quantity, 0);

  // Gates — the discount only applies when these pass.
  const countOk =
    (bundle.minProductCount == null || count >= bundle.minProductCount) &&
    (bundle.maxProductCount == null || count <= bundle.maxProductCount);
  const amountOk =
    (bundle.minOrderAmount == null || subtotalCents >= bundle.minOrderAmount * 100) &&
    (bundle.maxOrderAmount == null || subtotalCents <= bundle.maxOrderAmount * 100);
  if (!countOk || !amountOk) return { subtotalCents, discountCents: 0, totalCents: subtotalCents };

  let discountCents = 0;
  if (bundle.discountType === 'PERCENTAGE') {
    discountCents = Math.round(subtotalCents * (bundle.discountValue / 100));
  } else if (bundle.discountType === 'FIXED_AMOUNT') {
    discountCents = Math.round(bundle.discountValue * 100);
  }
  discountCents = Math.min(discountCents, subtotalCents);
  return { subtotalCents, discountCents, totalCents: subtotalCents - discountCents };
}
```

<Note>
  This preview is display-only. The Shopify Function is authoritative at checkout; sourcing the preview from the same `discountType` / `discountValue` / gate fields keeps them in sync.
</Note>

## Checklist

<Steps>
  <Step title="Bundle is Active in the Appstle admin" />

  <Step title="Selection satisfies count and amount gates before checkout" />

  <Step title="Every line carries _appstle-bb-id and _appstle_bundles_type = CLASSIC_BUILD_A_BOX" />

  <Step title="Verify the discounted total in the cart matches your preview" />
</Steps>

## Next steps

<CardGroup cols={2}>
  <Card title="Volume discount bundle" icon="layer-group" href="/bundles/volume-discount-bundle">
    Tiered "buy more, save more" discounts.
  </Card>

  <Card title="Fixed pricing bundle" icon="box" href="/bundles/fixed-pricing-bundle">
    Sell a curated set for one fixed price.
  </Card>
</CardGroup>
