# Product configurator

> Prices calculated from the customer's choices with a formula, such as a price per letter, per square metre or per material, shown on the page and charged at checkout.

Status: Ready (Smootify 2.0)  
Plan: Server plan  
Add-on: Product configurator  
URL: https://www.smootify.io/docs/astro/features/product-configurator  
Updated: 2026-10-21

The product configurator calculates the price from what the customer chooses: 30 cents per engraved letter, a price per square metre, a wood that costs 40% more. Each choice is a form field with a small formula. The price on the page follows the choices, and the customer pays that price at checkout. It covers what Shopify variants cannot: free sizes, long lists of options, extras that add up.

> [!NOTE]
> The configurator needs the **server** plan and **Product configurator** turned on in the Smootify dashboard. Without them, the configurator fields are removed from the page and `configurator-share` does nothing. It runs on the site's licensed domain.

## How it fits together

What Smootify needs:

- the configurator fields inside the `<form>` of the product’s `smootify-add-to-cart`;
- one element per field, with one input inside;
- a formula for each field, or for each choice of a field.

The price starts from the price of the selected variant. Each field that is **on** (filled in, checked, chosen) applies its formula, in the order the fields appear on the page, and passes the result to the next one. Here, with a variant at $120.00 per square metre and the fields of step 2:

| Step | Price |
|---|---|
| the variant price: `price` starts here | $120.00 |
| `configurator-dimension`, `price * width * height`, with 1.5 × 0.8 m | $144.00 |
| `configurator-select`, the choice `Walnut\|price * 1.4` | $201.60 |
| `configurator-checkbox`, `price + 5`, not checked: off, so skipped | $201.60 |
| `smootify-price`, `data-prop="price"` | $201.60 |
| `smootify-price`, `data-prop="total"`: the price times the quantity, 2 | $403.20 |

Each field is an element with one of these tags:

| Element | Put inside | On while | Formula |
|---|---|---|---|
| [`configurator-input`](https://www.smootify.io/docs/astro/reference/elements/configurator-input) | an `input` or a `textarea` | it holds a valid value | `data-formula`, which can use `length` |
| [`configurator-checkbox`](https://www.smootify.io/docs/astro/reference/elements/configurator-checkbox) | a checkbox | it is checked | `data-formula` |
| [`configurator-radio`](https://www.smootify.io/docs/astro/reference/elements/configurator-radio) | radio buttons | a radio is chosen (clicking it again unchecks it) | in each radio’s `value`, after a `\|` |
| [`configurator-select`](https://www.smootify.io/docs/astro/reference/elements/configurator-select) | a `select` | a choice with a value is chosen | in each option’s value, after a `\|` |
| [`configurator-popover`](https://www.smootify.io/docs/astro/reference/elements/configurator-popover) | a `[popover]` and a `select` | a choice is made | in the options of the `select`, after a `\|` |
| [`configurator-dimension`](https://www.smootify.io/docs/astro/reference/elements/configurator-dimension) | `input type="number"` fields | every one holds a valid number | `data-formula`, which can use each field’s name |
| [`configurator-file-input`](https://www.smootify.io/docs/astro/reference/elements/configurator-file-input) | an upload area | at least one file is uploaded | `data-formula`, which can use `file` |
| [`configurator-field`](https://www.smootify.io/docs/astro/reference/elements/configurator-field) | configurator fields, to group them | never: it only groups | a `data-formula` on it does nothing |

What is up to you: which fields a product uses, their order, labels, wrappers and styles.

Each field’s value is also saved on the cart line under the field’s `name`, like any other field of the form, so the merchant sees the choices on the order.

## 1. Write the formulas

A formula is a calculation that gives the new price. It can use:

- `price`: the price so far, starting from the variant price;
- `quantity`: the quantity in the form;
- `length`: in a `configurator-input`, the number of characters typed;
- in a `configurator-dimension`, the `name` of each number field, such as `width` and `height`;
- `file`: in a `configurator-file-input`, the number of files uploaded.

| Formula | Price |
|---|---|
| `price + 5` | 5 more |
| `price + 0.3 * length` | 0.30 more per character |
| `price * width * height` | the variant price per square metre |
| `price * 1.4` | 40% more |
| `max(price * width * height, 50)` | per square metre, at least 50 |

**Where the formula goes.** On a field with one value, in the attribute `data-formula`. On a field with choices (radio, select, popover), in the value of each choice, after a `|`: `Walnut|price * 1.4` is the choice Walnut, with its formula. Write it in the `value` of a radio button, or in the `value` of an `<option>`. The customer and the order see only `Walnut`.

**How formulas are read.**

- `+`, `-`, `*`, `/` and `^` (power) work as in maths, and so do functions such as `round(…)`, `ceil(…)`, `min(…, …)` and `max(…, …)`.
- `%` is the remainder of a division, not a percentage. A `%` at the end of a number is ignored, so `price * 10%` multiplies by 10. For 10% more, write `price * 1.1`.
- A name that does not exist counts as 0.
- A formula that cannot be read changes nothing. A result that is not a number, such as a division by 0, keeps the price as it was.

The formula builder writes a formula from a field and a rule in words, tries it with a price, and warns about names that count as 0 and about `%`:

_The formula builder of this page writes a formula from a field and a rule, and tests it with a price: open the page in a browser to use it. The rules it writes:_

| Rule | Formula |
|---|---|
| add an amount, such as 5 | `price + 5` |
| an amount per character of a `configurator-input` | `price + 0.3 * length` |
| an amount per file of a `configurator-file-input` | `price + 2 * file` |
| the variant price per unit of area of a `configurator-dimension` | `price * width * height` |
| an amount per unit of area | `price + 40 * width * height` |
| a percentage more, such as 40% | `price * 1.4` |
| a percentage less, such as 10% | `price * 0.9` |
| a fixed price | `25` |
| at least a price, such as 50 | `max(…, 50)` around the formula |
| rounded to a whole number, or up | `round(…)`, `ceil(…)` around the formula |

On a field with choices, each choice is its name, a `|` and its formula: `Walnut|price * 1.4`. A choice without a formula keeps the price.

## 2. Build the fields

```html
<configurator-dimension data-formula="price * width * height">
  <div><label>Width (m)</label><input type="number" name="width" min="0.5" max="3" step="0.01"><error-message>From 0.5 to 3 m</error-message></div>
  <div><label>Height (m)</label><input type="number" name="height" min="0.5" max="3" step="0.01"><error-message>From 0.5 to 3 m</error-message></div>
</configurator-dimension>
<configurator-select>
  <select name="wood"><option value="">Choose a wood</option><option value="Oak|price">Oak</option><option value="Walnut|price * 1.4">Walnut</option></select>
</configurator-select>
<configurator-checkbox data-formula="price + 5">
  <label><input type="checkbox" name="gift-wrap"> Gift wrap (+ $5)</label>
</configurator-checkbox>
<smootify-price><div data-prop="price">$0.00</div><div data-prop="total">$0.00</div></smootify-price>
```

- **One input per element.** Each element handles one input. For three questions, use three elements.
- **One name per field.** Give every field its own `name`. Two fields with the same name count as one.
- **Dimensions.** Only `input type="number"` fields count; Smootify removes the others. Their names are the names in the formula, so use one plain word, such as `width`. The attributes `min`, `max` and `step` set what is valid; for decimals, set `step`, such as `0.01`.
- **Preselected values count.** A value a field already has when the page opens is in the price from the start: a `value`, a `checked` checkbox or radio, a `selected` option, and what the browser puts back on Back. For a step that should start off, leave it empty and unchecked.
- **Numbers start at their minimum.** An empty Number field with `min`, in `configurator-dimension` or `configurator-input`, starts at its minimum, and the price with it: a desk from 100 × 60 cm opens at 100 × 60, priced. Its placeholder shows only if the customer empties it.
- **Radios.** Clicking the chosen radio again unchecks it and turns its formula off.
- **Popover.** The `select` inside holds the choices and their formulas, and Smootify hides it. The first link or button inside the `[popover]` is the template of every choice. See [popovers](https://www.smootify.io/docs/astro/build/popovers).
- **Files.** `configurator-file-input` is an upload area built like the [file uploads](https://www.smootify.io/docs/astro/features/file-uploads). Its files are saved on the line like a `file-uploader`’s.

**Error messages.** The element `error-message` shows only while its field is not valid, for example when the customer tries to add a size out of range. Put it anywhere inside `configurator-input` or `configurator-radio`. In `configurator-dimension`, put it right after its number field. The element and the field also get the class `is-invalid`. The browser shows no message of its own, so every field with limits or a required value needs its error state, with a text that says the limits, such as “Between 60 and 90 cm”: without it, Add to cart stops and the customer does not know why, or what to type.

```html
<configurator-input data-formula="price + 0.3 * length">
  <label>Engraving</label>
  <input name="engraving">
  <error-message>Write the text to engrave</error-message>
</configurator-input>
```

**Required fields.** Add `required` to the field, with `min` and `max` on a size, so that Add to cart and Buy now stop on an empty field and the browser points to it. On a product page that every product shares, render the configurator only for the products that have one, with an Astro condition: a configurator that is not rendered has no required fields, and the other products add to the cart as usual.

## 3. Prices by quantity

A price table by quantity is not supported on an Astro site: the configured price comes from the variant price and the field formulas.

## 4. On the page and at checkout

A `smootify-price` in the product shows the configured price in `data-prop="price"`, and the price times the quantity in `data-prop="total"`.

**Add to cart.** The line goes to the cart with the configured price. When the fields leave the price as it was, the line is an ordinary one.

**Checkout.** A cart with a configured price checks out through a Shopify draft order: the customer pays on the draft order’s invoice page, not on the usual checkout. Every line of the cart goes into that order, except subscription lines. The cart is emptied when the customer leaves for the invoice.

**Buy now.** A `button` with `type="submit"` and `name="buy-now"` goes straight to the invoice for this product and its add-ons. The rest of the cart stays as it was.

The buy-again button of the [order pages](https://www.smootify.io/docs/astro/build/orders) leaves configured products out: a cart cannot carry their price.

**Show the choices in the page.** A summary such as “How your price is made” can follow what the customer chooses, as they type. Inside the `smootify-product` of the form, add these attributes to text elements. A field is named by its `name`, such as `Engraving` or `Gift wrap`; for a dimension, by the `name` of one of its number fields.

| Attribute | Shows |
|---|---|
| `form-value="Engraving"` | what the customer wrote or chose: the text, the label of the chosen option or of a checked box, the names of the files |
| `form-value="Engraving"` + `data-variable="length"` | the value the formula reads, such as the number of characters (`file` for the number of files) |
| `configurator-price="base"` | the price of the chosen variant |
| `configurator-price="Engraving"` | what that choice adds to the price |
| `configurator-price="total"` | the configured price of one item |
| `configurator-formula="width * depth / 10000"` | a calculation with the same names as the formulas, with `price`, `base` and `quantity`; `data-digits` sets the decimals (2 by default) |
| `if-configurator="Engraving"` | shown only while that choice counts: filled, checked or chosen, and not hidden by a condition. `unless-configurator` is the opposite |

```html
<div>Top <span configurator-price="base">€ 0.00</span></div>
<div if-configurator="width">Surface <span form-value="width">0</span> × <span form-value="depth">0</span> cm = <span configurator-formula="width * depth / 10000">0</span> m² <span configurator-price="width">€ 0.00</span></div>
<div if-configurator="Engraving">Engraving “<span form-value="Engraving"></span>”, <span form-value="Engraving" data-variable="length">0</span> characters <span configurator-price="Engraving">€ 0.00</span></div>
<div>Your desk <span configurator-price="total">€ 0.00</span></div>
```

**Before the customer chooses.** Add `data-fallback` with a text to show in its place: on `form-value`, while the field is empty (with `data-variable`, also while the field does not count in the price); on `configurator-price="Engraving"`, while that choice does not count (without it, the price shows 0); on `configurator-formula`, while one of its names has no value yet. `base` and `total` always have a value. An empty `data-fallback` leaves the element empty, and a row hidden by `if-configurator` stays hidden.

**Two fields, one name.** On a template every product shares, two configurator fields can have the same `name`, such as the engraving of a desk and of a shelf in blocks shown on different products. Each counts on its own, and the summary reads the one on show.

The same `form-value` shows the answers of [dynamic properties](https://www.smootify.io/docs/astro/features/dynamic-properties).

## 5. Save and share a configuration

A customer who configures a desk rarely buys it on the first visit. `configurator-share` gives them a link to the same configuration, to open again on another device or to send to someone else.

Add the element `configurator-share` inside the product’s `smootify-add-to-cart`, with a `button` inside. A click builds a link to this page with every choice in it, one parameter per field under its `name` (`?Width=180&Finish=walnut`), plus the variant when the product has more than one. On phones it opens the share sheet; elsewhere it copies the link. With `data-copy` it always copies. `data-title` is the title of the share. Style the moment with the classes `is-copied` or `is-shared`, which last two seconds.

```html
<smootify-add-to-cart>
  <form>
    <configurator-field>…</configurator-field>
    <configurator-share data-title="My desk"><button type="button">Share this desk</button></configurator-share>
  </form>
</smootify-add-to-cart>
```

Whoever opens the link sees the fields already filled, as if they had typed and chosen them, and the price that follows. A value a field would refuse stays off, such as a width below its `min` or an option the select no longer has. A file the customer uploaded is not in the link: it is uploaded again.

To keep a configuration in your store instead, with your own price check, use a quote: the cart’s quote button turns the configured lines into a draft order. See [Quotes](https://www.smootify.io/docs/astro/features/quotes).

## Before you publish

Test an order on the licensed domain, from the product page to the invoice page. The invoice page already has the discount codes of the cart, and accepts more codes and gift cards. It is not the usual checkout: check the payment methods the customer sees there.

> [!STUCK]
> - **The fields disappear.** The site is not on the server plan, or Product configurator is not turned on.
> - **The price does not change on a gift card.** Shopify issues a gift card for its variant's value whatever was paid, so the configurator leaves a gift card product's price alone. For a gift card at any amount, see [Name your price](https://www.smootify.io/docs/astro/features/name-your-price#a-gift-card-of-any-amount).
> - **A formula does nothing.** It is on `configurator-field`, it cannot be read, or its field is not on (empty, unchecked, invalid).
> - **The price is 10 times too high.** The formula uses `%` as a percentage. Write `price * 1.1` for 10% more.
> - **A size field is never valid.** The number has decimals and the field has no `step`, or the value is outside `min` and `max`.
> - **Add to cart does nothing on a configurable product.** A field is out of range or empty and the page has no `error-message` or `is-invalid` style to say so.
> - **An error message always shows.** In `configurator-dimension`, it is not right after its number field.
> - **A product without a configurator cannot be added to the cart.** Its required fields are in a block hidden with CSS or `display: none`. Render the block only for the products that have a configurator, with an Astro condition: a block that is not rendered has no required fields.
> - **Two choices count as one.** The fields share the same `name`.
> - **The shared link opens with empty fields.** The fields have no `name`, or a different one from when the link was made: the link names each choice after its field.
