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

# Fixed pricing bundle with a custom storefront

> Sell a curated set of products for one fixed total price using your own storefront UI. Read the rule, render a selector, add to cart with Appstle's bundle attributes, and let the automatic discount price the set.

A **fixed pricing bundle** sells a defined set of products for one fixed total price, regardless of the sum of the individual item prices. For example, *"Any 3 candles for \$40"* — if the candles total \$54, the customer saves \$14 automatically.

This guide shows how to run a fixed pricing bundle entirely 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. If you have not yet read the [headless overview](/bundles/headless-overview), start there — it explains the automatic-discount model these steps rely on.

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

## How pricing works

The merchant sets a single `price` on the rule — the total the customer pays for the bundle. Appstle's discount Function reduces the bundled lines so their combined total equals `price`:

```
customer pays   = price
customer saves  = (sum of selected line prices) − price
```

The discount is applied automatically at checkout once the lines carry the bundle attributes. You do not compute or submit the discount.

## 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 fixed pricing bundles by `bundleType` + `bundleSubType`.

```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 fixedBundles = rules.filter(
  r => r.bundleType === 'SINGLE_PRODUCT_BUILD_A_BOX' &&
       r.bundleSubType === 'FIXED_BUNDLE' &&
       r.status === 'ACTIVE'
);
```

The fields you need from each rule:

| Field                                 | Use                                            |
| ------------------------------------- | ---------------------------------------------- |
| `uniqueRef`                           | Attach to every cart line as `_appstle-bb-id`. |
| `name`                                | Display label; attach as `__appstle-bb-name`.  |
| `price`                               | The fixed total the customer pays.             |
| `minProductCount` / `maxProductCount` | How many items the customer must pick.         |
| `products` / `variants`               | Eligible products and variants (JSON).         |

<Tip>
  Take the eligible product and variant IDs from the rule's `products` / `variants` fields and hydrate titles, images, and prices with **Shopify's own** Storefront API or Liquid product objects. If you would rather receive a pre-parsed product list from Appstle, the App Proxy endpoint `GET /bundles/bb/api/build-a-box/get-bundle/{handle}` returns one — use it as a last resort and cache the response.
</Tip>

## Step 2 — Render your selector

Build whatever UI matches your theme. Drive its rules from the configuration:

* Show the eligible products.
* Enforce the item count between `minProductCount` and `maxProductCount`.
* Display the headline price (`price`) prominently, and optionally the savings (see [below](#previewing-savings)).

## Step 3 — Add the bundle to the cart

When the selection is valid, add the chosen variants to the cart with the [required attributes](/bundles/headless-overview#required-cart-line-attributes). For a fixed pricing bundle, each component line carries `_appstle-bb-id`, `_appstle_bundles_type`, and `__appstle-bb-name`.

<CodeGroup>
  ```js Shopify theme (Ajax Cart API) theme={null}
  const lineFor = (variantId, quantity) => ({
    id: variantId,
    quantity,
    properties: {
      '_appstle-bb-id': bundle.uniqueRef,
      '_appstle_bundles_type': 'SINGLE_PRODUCT_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: selectedItems.map(i => lineFor(i.variantId, i.quantity)),
    }),
  });
  ```

  ```graphql Headless (Storefront API) theme={null}
  mutation AddFixedBundle($cartId: ID!) {
    cartLinesAdd(
      cartId: $cartId
      lines: [
        {
          merchandiseId: "gid://shopify/ProductVariant/111"
          quantity: 1
          attributes: [
            { key: "_appstle-bb-id", value: "fixed-candle-set-ref" }
            { key: "_appstle_bundles_type", value: "SINGLE_PRODUCT_BUILD_A_BOX" }
            { key: "__appstle-bb-name", value: "Any 3 Candles" }
          ]
        }
        {
          merchandiseId: "gid://shopify/ProductVariant/222"
          quantity: 1
          attributes: [
            { key: "_appstle-bb-id", value: "fixed-candle-set-ref" }
            { key: "_appstle_bundles_type", value: "SINGLE_PRODUCT_BUILD_A_BOX" }
            { key: "__appstle-bb-name", value: "Any 3 Candles" }
          ]
        }
      ]
    ) {
      cart { id }
      userErrors { field message }
    }
  }
  ```
</CodeGroup>

<Warning>
  Every line that belongs to the bundle must carry the **same** `_appstle-bb-id`. A line without the attributes is treated as a normal product and is not priced into the fixed total.
</Warning>

## Step 4 — Discount applies automatically

Once the lines are in the cart, Appstle's automatic discount reduces the bundled lines so the set totals `price`. It shows in the cart, persists through checkout, and is reflected on the order. There is nothing else to call.

## Previewing savings

To show the customer what they save before checkout, compute it from the rule (remember cart prices are in cents):

```js theme={null}
// linePrices: array of unit prices (in cents) × quantities for the selected items
const subtotal = linePrices.reduce((sum, cents) => sum + cents, 0);
const fixedTotalCents = Math.round(bundle.price * 100);
const savingsCents = Math.max(0, subtotal - fixedTotalCents);

// e.g. "You save $14.00"
const savingsLabel = `You save ${(savingsCents / 100).toFixed(2)}`;
```

<Note>
  This is a display-only preview. The Shopify Function applies the authoritative discount at checkout from the same `price`, so the two always agree.
</Note>

## Checklist

<Steps>
  <Step title="Bundle is Active in the Appstle admin">
    Only active rules have a live automatic discount.
  </Step>

  <Step title="Selection respects min/max product count">
    Validate before adding to cart.
  </Step>

  <Step title="Every line carries the bundle attributes">
    `_appstle-bb-id` (= `uniqueRef`) and `_appstle_bundles_type` (= `SINGLE_PRODUCT_BUILD_A_BOX`) on each line.
  </Step>

  <Step title="Verify the discount in the cart">
    Confirm the bundled lines total the configured `price`.
  </Step>
</Steps>

## Next steps

<CardGroup cols={2}>
  <Card title="Dynamic pricing bundle" icon="boxes-stacked" href="/bundles/dynamic-pricing-bundle">
    Build-your-own with a percentage or amount discount.
  </Card>

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