# Product add-ons

> Offer other products next to the main one, in its add-to-cart form, with checkboxes, selects, dropdowns, popovers or swatches, and send them to the cart as one line with the product.

Status: Ready (Smootify 2.0)  
Add-on: Magic Add-ons  
1.x page: https://docs.smootify.io/docs/ecommerce/add-ons/magic-add-ons  
URL: https://www.smootify.io/docs/features/add-ons  
Updated: 2026-10-08

Add-ons are other products the customer can add together with the one on the page: a gift wrap, an extended warranty, a case for a phone. They sit in the product’s add-to-cart form, and they go to the cart **together with the product**, as one line. This page covers the Shopify side, the five kinds of add-on picker, prices and quantity limits.

> [!NOTE]
> In the Smootify dashboard, turn on **Magic Add-ons** and **Magic Box**. Both are free. Magic Add-ons adds the product metafields **Add ons** and **Combinations** and the **Combinations** metaobject definition to Shopify. Magic Box adds the checkout function that merges the product and its add-ons into one line.

## 1. Choose the add-ons in Shopify

1. In Shopify, open the main product and fill its **Add ons** metafield with the products to offer. Any other product metafield that holds a list of products works too, such as one your store already uses for accessories.
2. In **Settings > Custom data**, check that the **Add ons** and **Combinations** product metafields and the **Combinations** metaobject have **Storefront access**. Smootify creates them with it, but a definition that already existed in the store keeps its own setting.

An add-on is an ordinary Shopify product, with its own price, variants and stock.

## 2. Add the add-ons to the form

Add-ons go inside the Form of the product’s `smootify-add-to-cart`. Only the fields inside the Form reach the cart.

1. Inside the Form, add a Custom element with the tag `smootify-product`. Give it `data-id="metafields.custom.add_ons"` and `data-parent-id`, bound to the **Shopify ID** field of the page’s product. Smootify repeats this element once per product in the **Add ons** metafield. For another metafield, write its namespace and key instead, such as `data-id="metafields.custom.accessories"`: its type must be a list of products, and its definition needs Storefront access.
2. Inside it, put one add-on picker (next section) and whatever you want to show about the add-on: `product="title"`, an image, a `smootify-price`.

```html
<smootify-add-to-cart>
  <div class="w-form">
    <form>
      <div>
        <smootify-product data-id="metafields.custom.add_ons" data-parent-id="{Shopify ID}">
          <addon-checkbox>
            <label class="w-checkbox"><input type="checkbox" class="w-checkbox-input"><span class="w-form-label">Add-on</span></label>
          </addon-checkbox>
          <div product="title">Add-on name</div>
          <smootify-price><div data-prop="price">$0.00</div></smootify-price>
        </smootify-product>
      </div>
      <div>Total: <span bundle="total">$0.00</span></div>
      <input type="submit" value="Add to cart">
    </form>
    <div class="w-form-done"><div>Added</div></div>
    <div class="w-form-fail"><div>Something went wrong</div></div>
  </div>
</smootify-add-to-cart>
```

To offer one fixed product instead of the metafield list, give the inner `smootify-product` that product’s id in `data-id`.

What is up to you: the layout of the list (a grid, a row of cards, a Dropdown), what each add-on shows, and which picker each one uses.

**The fields of a picker take any Name.** Webflow asks for a Name on every checkbox, select or quantity field: write what you like, such as `addon`. Smootify renames the picker’s fields itself, and they never reach the cart line as a property, not even when the customer adds to cart before the add-on has loaded.

## 3. Pick a picker

Each add-on needs one of these Custom elements inside its `smootify-product`:

| Picker | Built with | What it adds, and how to choose no add-on |
|---|---|---|
| `addon-checkbox` | a Webflow Checkbox | the add-on's current variant, while checked. The label text stays as you write it |
| `addon-select` | a Select field | the variant chosen in the select. Smootify writes one option per variant; an option you add without a value, such as "No thanks", means no add-on |
| `addon-dropdown` | a Webflow Dropdown | the variant chosen in the dropdown. The first Dropdown link is the template; `show-price` adds the price after each name. With `allow-unselect`, a "None" entry comes first; without it, the first variant is chosen from the start |
| `addon-popover` | an element with a popover | the variant chosen in the popover. The first link or button in the popover is the template; `show-price` and `allow-unselect` work as in the dropdown |
| `addon-swatches` | Custom elements with the tag `button` | the value or variant of the chosen button, which gets the class `is-active`. The first button is the template: one button per value for a product with one option, one per variant for more. A click on the chosen button removes the add-on; `allow-unselect="false"` prevents that and chooses the first value from the start |

`single-addon-swatch` on `smootify-add-to-cart` keeps only one swatch add-on chosen at a time. The swatch buttons of a product with one option take the swatch parts of the [variant swatches](https://www.smootify.io/docs/build/swatches) (`option="title"`, `option="image"`, `option="bg-color"`).

```html
<smootify-product data-id="{Shopify ID}">
  <addon-swatches>
    <button><span option="title">Value</span><smootify-price><span data-prop="price">$0.00</span></smootify-price></button>
  </addon-swatches>
</smootify-product>
<smootify-product data-id="{Shopify ID}">
  <addon-dropdown show-price="true" allow-unselect="true">
    <div class="w-dropdown">
      <div class="w-dropdown-toggle"><div>Choose</div></div>
      <nav class="w-dropdown-list"><a href="#" class="w-dropdown-link">Variant</a></nav>
    </div>
  </addon-dropdown>
</smootify-product>
```

A `smootify-price` inside a swatch button shows the price of that button’s value. A checked checkbox gives its `smootify-product` the class `is-checked`.

## 4. Prices and quantities

**The total.** With add-ons chosen, `data-prop="total"` of the main `smootify-price` includes them. `bundle="total"`, anywhere in `smootify-add-to-cart`, shows the same total: the product and its chosen add-ons. An Image with `bundle="images"` is copied once per chosen add-on, with that add-on’s picture, each copy stacked over the one before. Both stay hidden while the form has no add-on picker.

**Quantity.** An add-on without its own quantity field follows the quantity of the main product. To let the customer choose it, put a Custom element with the tag `quantity-input` inside the picker, with a Text field inside, as on the [product page](https://www.smootify.io/docs/build/product-page). In an `addon-checkbox`, the field starts at 0, and a quantity above 0 checks the box.

**Limits for all the add-ons together.** On the Text field of an add-on’s `quantity-input`, these custom attributes limit the sum of the add-on quantities, for each unit of the main product:

- `global-min`: at least this many add-ons;
- `global-max`: at most this many;
- `global-total`: exactly this many.

Put them on one add-on’s field only. While the add-ons are outside the limits, the add button is disabled and `smootify-add-to-cart` gets a class. Inside the form, `condition="below-bundle-limits"` and `condition="above-bundle-limits"` show a message only in that state, and `bundle-limit` shows the numbers. A box of chocolates with `global-total="3"` needs exactly 3 add-ons:

| Chosen | Class on `smootify-add-to-cart`, add button | Condition and numbers that show |
|---|---|---|
| 2 of 3: below the limit | `is-below-bundle-limits`; the button is disabled | `condition="below-bundle-limits"`; `bundle-limit="current-total"` 2, `bundle-limit="missing-units"` 1 |
| 3 of 3: within the limit | no class; the button is enabled | neither condition; `bundle-limit="current-total"` 3 |
| 4 of 3: above the limit | `is-above-bundle-limits`; the button is disabled | `condition="above-bundle-limits"`; `bundle-limit="current-total"` 4, `bundle-limit="above-units"` 1 |

**Add-ons and subscriptions.** Add-ons are for one-time purchases. While the customer picks a [subscription](https://www.smootify.io/docs/features/subscriptions) plan, each add-on picker takes the `is-disabled` class, its fields are disabled and the add-on stays out of the total and the cart. Switching back to one-time brings the choice back. On product pages that sell both, style `.is-disabled` as a combo class, for example with lower opacity or by hiding the block, or show a note such as “Gift wrap is for one-time orders”.

## 5. One line in the cart

When the customer adds the product with add-ons, they go to the cart together. With Magic Box turned on, the cart and checkout show them as one line, with the product and its add-ons inside. In the [cart](https://www.smootify.io/docs/build/cart), a `box-item` inside the `cart-item` template lists them. The quantity of that line cannot be changed in the cart.

The line takes its title and image from the first of these that applies:

1. **A combination.** In Shopify, under **Content > Metaobjects > Combinations**, an entry with a **Title**, an **Image** and the **Variants** of the add-ons, without the main product, listed in the main product's **Combinations** metafield. When the chosen add-on variants are exactly those, the line takes that title and image. It needs `use-magic-box="true"` on `smootify-add-to-cart`: without it, Smootify does not read the combinations.
2. **`box-title` and `box-image`** on `smootify-add-to-cart`, when no combination matches. The image must be a Shopify file (**Content > Files**): checkout ignores an image from anywhere else.
3. **The main product**, otherwise: the line keeps its name and image.

For a tablet with a pen and a keyboard as add-ons, three combinations cover every choice: the pen, the keyboard, and the pen with the keyboard.

## 6. Keep add-on products out of lists and search

An add-on such as a gift wrap is a product of its own, so it can show up wherever the store lists products. Keep it out in two places:

- **Search and the CMS.** In Shopify, give the add-on the metafield `seo.hidden` with the value `1`. To set it in the admin, first add a product metafield definition with namespace `seo`, key `hidden` and type integer, with **Storefronts** access on. Shopify then leaves the product out of its search, and so do Smootify’s predictive search and search page. The change takes a few minutes. The CMS sync skips it too, so it gets no page and no item in your Collection lists; an item imported before goes with the next full sync from Smootify Studio (see [CMS collections and sync](https://www.smootify.io/docs/start/cms)). The add-on still loads by its id, so it keeps working on the product page.
- **Collections and product lists.** Keep the add-on out of your collections, including automated ones. A collection page lists only the products in it. A product list that runs a query of its own, such as best sellers, newest products or a custom query, can still show the add-on: the store’s best sellers (`data-id="best-seller"`) come from the whole store. Use a custom query that leaves it out, such as `data-query="-product_type:Add-on"` with `data-sort="BEST_SELLING"`, or pick the products by collection with `data-id="specific-collection"`.

## Before you publish

> [!STUCK]
> - **No add-on shows.** The product's **Add ons** metafield is empty, or the metafield has no Storefront access, or `data-parent-id` is not bound to the `cms:Shopify ID` field.
> - **An add-on disappears.** It is inside an element hidden by Webflow's conditional visibility: the add-to-cart form removes those elements, pickers included.
> - **Add-ons are not added.** The pickers are outside the Form, so their fields never reach the cart.
> - **The product and its add-ons are separate lines at checkout.** Magic Box is not turned on in the Smootify dashboard.
> - **The combination title never shows.** `use-magic-box="true"` is missing, or the combination lists the main product, or its variants do not match the chosen add-ons exactly.
> - **An add-on does nothing.** A subscription plan is picked: add-ons go only with the one-time purchase, and the picker has `is-disabled`.
> - **An add-on is always added.** An `addon-dropdown` or `addon-popover` without `allow-unselect` starts on the first variant.
