> For the complete documentation index, see [llms.txt](https://kb.craftformswp.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://kb.craftformswp.com/documentation/form-building/create-form/form-settings-post-context/smart-variables.md).

# Smart Variables

## Smart Variables <a href="#smart-variables" id="smart-variables"></a>

A smart variable is a named value that your form works out for itself while the visitor fills it in. It can be a formula (`width * height`), a price looked up in a table, an option's price, a value that depends on a condition, a value read from the page the form is on, or a value that depends on who is viewing the form, such as a wholesale price for wholesale customers.

You use smart variables to:

* build the **price formula** from readable parts: `area * price_per_m2 + frame_price` instead of one long formula;
* **show** calculated values to the visitor in an Info Block (`{{field.area}}`);
* send calculated values in **emails, PDFs and the saved submission** (`{{email.area}}`);
* **show, hide or disable fields** based on a calculated value (Field Conditions can compare against a smart variable).

Smart variables are a **PRO** feature. In the free version the **Smart Variables** panel shows an *Upgrade to Pro* button instead of the editor.

![The CraftForms Smart Variables window](https://97821998-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FM2yV1foUgoGHBC66VBDw%2Fuploads%2F1vAD23WuOwJaoskOYOJd%2Fsmart-variables-window.jpg?alt=media\&token=be4f2d61-e2f0-4849-bcc1-d39339eeca0b)

### Open the Smart Variables window <a href="#open-the-smart-variables-window" id="open-the-smart-variables-window"></a>

1. Open the form (**CraftForms → Forms → Edit**).
2. In the right sidebar, on the **Form** tab, open the **Smart Variables** panel. It shows how many variables the form has.
3. Click **Manage Smart Variables**.

The window has two columns:

* **Variables** (left): your variables, in order. Click a variable (or its pencil icon) to edit it. Use the arrows to reorder, the bin to delete, and **+ Add variable** to create one.
* **Reference** (right): everything you can use in a formula. **Click any name to copy it.**
  * **Form fields**: every field that has a name. A booking date picker shows its parts, for example `dates.checkin` and `dates.checkout`, or `slot.date` and `slot.time`.
  * **Defined variables**: the smart variables in this form.
  * **WooCommerce** (only when WooCommerce is active): `wc_price`, `wc_sku` and `wc_stock`, which a product page fills in for you.
  * **Functions**: the functions you can call, such as `round`, `max` and `count_nights`.

Nothing is saved until you click **Save Changes** at the bottom of the window **and** then save the form. While a variable is open for editing, finish it first (**Add** / **Update** or **Cancel**). The window won't save while a variable is still being edited.

### Settings every variable has <a href="#settings-every-variable-has" id="settings-every-variable-has"></a>

| Setting                                | What it does                                                                                                                                                                                                                                                                |
| -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Type**                               | How the value is worked out: **Expression**, **Table**, **Linked**, **Conditional**, **Dynamic** or **User role**. Each type is described below.                                                                                                                            |
| **Variable name**                      | The name you use in formulas. Letters, digits, `_` and `$` only. Anything else is turned into `_` as you type, and a name can't start with a digit or be a JavaScript keyword such as `new` or `class`. Names must be unique in the form.                                   |
| **Default value** / **Fallback value** | What the variable resolves to when it can't be worked out, for example when a table has no matching cell or a formula fails.                                                                                                                                                |
| **Expose as field variable**           | Makes the variable available as `{{field.name}}` in Info Blocks and `{{email.name}}` in email and PDF templates.                                                                                                                                                            |
| **Submission label**                   | Appears once **Expose** is on. When set, the variable is saved with the submission (and shown in the entry and on WooCommerce orders) under this label, for example *Area (m²)*. Leave it empty to use the variable in templates without storing it in the submission data. |

**Order.** Variables can use each other, in any order: `total` can use `area` even if `area` is listed below it. The form repeats the calculation until the values settle (up to five rounds), so chains up to five variables deep always resolve.

### Expression <a href="#expression" id="expression"></a>

A formula or value. This is the type to use most of the time.

{% code collapsedlinecount="10" %}

```
width * height / 10000
```

{% endcode %}

{% code collapsedlinecount="10" %}

```
tier == 'vip' ? 99 : 49
```

{% endcode %}

* **Expression** (required): the formula.
* **Default value**: used when the formula has no result, for example before the visitor has filled in the fields it uses.

The formula language is the same one used by the Price Formula. See [Formulas: operators and functions](#formulas-operators-and-functions) below.

### Table <a href="#table" id="table"></a>

A grid of values that the variable looks up by row and column, like a price matrix: width across the top, height down the side, price in the cells.

![A CraftForms table smart variable](https://97821998-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FM2yV1foUgoGHBC66VBDw%2Fuploads%2F00TIFPOduxOPZ7tEEXaS%2Fsmart-variables-table.jpg?alt=media\&token=84fde298-aed1-437e-9ef9-1bb2c39df75f)

* **Table data**: the grid. The top row holds the column keys, the first column holds the row keys. Use **Import CSV** to load a spreadsheet and **Export CSV** to download the table.
* **Column variable** and **Row variable** (both required): the field or variable whose value picks the column and the row. When the chosen field is a dropdown, radio or checkbox field, **Fill from options** writes its option values into the header for you.
* **Table cell lookup strategy**:
  * **Exact match**: the key must equal the value exactly. Use it for option values (`oak`, `walnut`) and fixed sizes.
  * **Closest up (smallest row/column ≥ value)**: rounds the value **up** to the next key. A width of 73 in a table with columns 50, 75, 100 uses the 75 column. A value above the largest key uses the largest key. This is the usual choice for "priced per size band" tables.
  * **Closest (nearest)**: uses the nearest key in either direction. On a tie it picks the lower key.
* **Default value**: returned when no cell matches.

Good to know:

* **Closest up** and **Closest** need numeric keys. They also don't look anything up while the value is empty or 0, so the variable shows its default until the visitor enters a size.
* A table with **one** value column is a simple list: the row key alone finds the value, and the column variable is ignored.
* When the row variable is a **checkbox** field and the visitor ticks several options, the variable adds up the matching cells (numbers) or lists them separated by commas (text).

### Linked <a href="#linked" id="linked"></a>

Reads the **label** or the **price** of the option the visitor chose in a dropdown, radio or checkbox field. Use it to bring option prices into your formula without retyping them.

* **Linked field** (required): the choice field.
* **Lookup value**: **Label (human-readable name)** or **Price**.
* **Default value**: returned when no option is chosen or nothing matches.

The **Linked** type only appears when the form has at least one choice field with options. The lookup is built from the field's options when the page loads, including any option overrides from a Catalog item or WooCommerce product, so a changed option price is picked up without editing the variable.

### Conditional <a href="#conditional" id="conditional"></a>

Picks a value with **IF / THEN** rules, checked from top to bottom.

![A CraftForms conditional smart variable](https://97821998-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FM2yV1foUgoGHBC66VBDw%2Fuploads%2FmJuTbEDwOypBg7jUckm6%2Fsmart-variables-conditional.jpg?alt=media\&token=6f8bb5d6-dcf2-405e-90fe-52f895f3432d)

* Click **+ Add condition** for each rule:
  * **IF this condition is truthy**: a condition, for example `width > 100` or `material == 'oak' && width > 100`.
  * **THEN use this formula/value**: a value or formula, for example `25` or `width * 0.3`.
* **ELSE use this formula/value**: used when no condition matches. It's a value, not a condition, so don't put comparisons or `&&` / `||` here.
* **Fallback value**: returned if the matching formula can't be evaluated at all. A conditional variable with no conditions simply resolves to this value.

The first rule that matches wins, so put the most specific rules first.

A THEN value that is plain text, such as `Large`, works even without quotes, but `'Large'` (quoted) is the clean way to write it.

### Dynamic <a href="#dynamic" id="dynamic"></a>

Reads a value from WordPress when the page loads, instead of from the visitor:

* **This post's meta**: a custom field of the page or post the form is embedded on, for example an event's price stored in `_event_price`. One form can then show a different price on every event page.
* **Site option**: a value from the WordPress options table, for example a rate another plugin saves. For safety, options are **blocked unless a developer allows them** with the `craftforms_dynamic_option_allowlist` filter, so an option that isn't on the list resolves to the default value.

Fill in the **Meta key** or **Option name**, and a **Default value** for when it's empty or missing.

The value is read on the server, both when the page loads and again when the form is submitted, so a visitor can't change it.

### User role <a href="#user-role" id="user-role"></a>

Gives the variable a **different value depending on the visitor's WordPress user role**. Typical uses:

* wholesale prices: `wholesale_customer` pays `wc_price * 0.8`, everyone else pays `wc_price`;
* a members' discount for any logged-in user;
* a trade-only surcharge or minimum.

![A CraftForms user role smart variable](https://97821998-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FM2yV1foUgoGHBC66VBDw%2Fuploads%2FgSra3IcVyQVQ4ALOmkGE%2Fsmart-variables-user-role.jpg?alt=media\&token=6656a831-f3f0-49cb-b177-2c805f49df4e)

1. Click **+ Add variable** and choose the **User role** type.
2. In the first rule, under **IF the visitor has any of these roles**, open **+ Add a role…** and pick one or more roles. Besides your site's roles you can pick:
   * **Guest (not logged in)**;
   * **Any logged-in user**;
   * **Other (type a role slug)…**: type a role's slug by hand, for example a role a plugin adds that isn't active right now. Press Enter or click **Add**.
3. In **THEN use this formula/value**, enter the value or formula for those roles, for example `0.8` or `wc_price * 0.8`.
4. Click **+ Add role rule** for more rules. Use ↑ / ↓ to reorder them and ✕ to remove one.
5. In **Everyone else** (required), enter the value for guests and for users whose roles match no rule.
6. Optionally set a **Fallback value**, used if the chosen formula can't be evaluated.

Then use the variable like any other, for example a price formula `ticket_price * role_multiplier`.

**How the rules are matched**

* Rules are checked **top to bottom; the first rule that matches wins**. A user with several roles gets the first rule that lists any of them, so put the most specific rules first and **Any logged-in user** last.
* **Not registered** roles: if a rule names a role that doesn't exist on the site (a typo, or a role from a plugin you've deactivated), the role chip turns red and says *not registered*. That rule never matches until the role exists again. Visitors fall through to the next rule or to **Everyone else**, and the form keeps working.
* The role is checked **on the server**. The page only ever contains the value for the person viewing it, so a retail customer can't find the wholesale price in the page source. When the form is submitted, the server checks the role again and recalculates the price. A visitor can't get another role's price by editing the page.

**Things to know**

* **Page caching.** The value is decided when the page is generated. Caching plugins normally don't cache pages for logged-in users, and that's what this feature needs. If your cache *does* serve cached pages to logged-in users, exclude your product and form pages for them. Otherwise a wholesale customer may see a cached guest price.
* **Logged-in but with an expired session.** If a page stays open for many hours, WordPress may no longer accept the visitor's session when they submit. The server then prices them as a guest, sees that the price doesn't match, and asks them to *reload the page and try again*. They're never charged the wrong price.
* **Forms embedded on other sites** (static sites, headless) always see the **Everyone else** value, because the visitor isn't logged in to your WordPress site there.
* **Developers** can price by something other than the WordPress role, such as a membership level or a B2B customer group, with the `craftforms_pricing_role_tokens` filter. It receives the list of role slugs the visitor matches (plus `@guest` or `@logged_in`) and the user object. Return the list with your own tokens added, and type those tokens into a rule with **Other (type a role slug)…**.

### Smart variables on WooCommerce products and Catalog items <a href="#smart-variables-on-woocommerce-products-and-catalog-items" id="smart-variables-on-woocommerce-products-and-catalog-items"></a>

A form can be reused by many products. Each **WooCommerce product** (**Product data → CraftForms**) and each **Catalog item** (**CraftForms → Catalog**) has its own **Smart Variables** section, which layers on top of the form's variables:

* **Merge**: same-name variables overwrite the form's; new ones are appended.
* **Override**: the form's smart variables are discarded and replaced entirely by these.

The product or item lists the form's variables you can override (*Overridable smart variables — click to copy*), plus the form fields and functions. The types are the same, apart from **Dynamic**, which is form-level only.

On a WooCommerce product page these variables are added for you, unless you define one with the same name:

| Variable   | Value                                                                                     |
| ---------- | ----------------------------------------------------------------------------------------- |
| `wc_price` | The product's WooCommerce price. Exposed, so `{{field.wc_price}}` works in an Info Block. |
| `wc_sku`   | The product's SKU, or the result of the product's SKU formula when it has one.            |
| `wc_stock` | The stock of the current SKU, when per-option stock is turned on for the product.         |

### Using a smart variable <a href="#using-a-smart-variable" id="using-a-smart-variable"></a>

| Where                                    | How                                                                              | Example                                       |
| ---------------------------------------- | -------------------------------------------------------------------------------- | --------------------------------------------- |
| Price Formula, other variables           | By name                                                                          | `area * price_per_m2`                         |
| Info Block, field and choice labels      | `{{field.name}}`, needs **Expose**                                               | `Area: {{field.area}} m²`                     |
| Email and PDF templates, action settings | `{{email.name}}`, needs **Expose**                                               | `Your board: {{email.area}} m²`               |
| Submission entry, WooCommerce order      | Automatic, needs **Expose** + **Submission label**                               | *Area (m²): 2.4*                              |
| Field Conditions                         | Pick it in the condition's field list, where it shows as *name (Smart Variable)* | Show "Free delivery" when `order_total > 100` |

A Field Condition can't show or hide a smart variable itself, only fields. If a condition compares against a variable that is calculated *from* the field the same rule shows or hides, the editor warns you. That rule could keep flipping the field on and off.

See [Dynamic tags and variables](/documentation/form-building/create-form/dynamic-tags-and-variables.md) for every tag you can use alongside smart variables.

### Formulas: operators and functions <a href="#formulas-operators-and-functions" id="formulas-operators-and-functions"></a>

| Write                            | Meaning                                           |
| -------------------------------- | ------------------------------------------------- |
| `+ - * / %`                      | Add, subtract, multiply, divide, remainder        |
| `**`                             | Power: `2 ** 3` is 8, `x ** 0.5` is a square root |
| `==` `!=` `>` `<` `>=` `<=`      | Compare                                           |
| `&&` `\|\|` `!` (or `and`, `or`) | Combine conditions                                |
| `condition ? a : b`              | If / else in one line                             |
| `~`                              | Join text: `first ~ ' ' ~ last`                   |
| `'text'`                         | A text value, in quotes                           |

* **`^` is not "power".** It's a bitwise operator and gives wrong results in a price formula. Use `**` or `pow(x, y)`. The editor warns you when a formula contains `^`.
* Write decimals with a leading zero (`0.5`, not `.5`), and don't use scientific notation (`1e5`).

Functions (also listed in the window's **Functions** section):

| Function                                                                                             | What it does                                                           |
| ---------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| `ceil(x)`, `floor(x)`, `round(x)`                                                                    | Round up, down, to nearest                                             |
| `abs(x)`, `sqrt(x)`, `pow(x, y)`                                                                     | Absolute value, square root, power                                     |
| `min(a, b, …)`, `max(a, b, …)`, `sum(list)`                                                          | Smallest, largest, total                                               |
| `count(list)`                                                                                        | Number of items, for example ticked checkboxes                         |
| `count_nights(checkin, checkout)`                                                                    | Nights between a booking's check-in and check-out                      |
| `words_count(text)`                                                                                  | Number of words                                                        |
| `includes(value, list_or_text)`                                                                      | Whether a checkbox field contains an option, or a text contains a word |
| `lower(text)`, `upper(text)`                                                                         | Change case                                                            |
| `is_empty(x)`, `is_null(x)`, `is_numeric(x)`, `is_int(x)`, `is_float(x)`, `is_email(x)`, `is_url(x)` | Checks that return true / false                                        |
| `is_date_between(date, "from:to")`, `is_time_between(time, "from-to")`                               | Whether a date or time falls in a range                                |
| `day(d)`, `month(d)`, `year(d)`, `week_day(d)`, `hour(t)`                                            | Parts of a date or time                                                |

### Security: why your prices can't be edited by visitors <a href="#security-why-your-prices-cant-be-edited-by-visitors" id="security-why-your-prices-cant-be-edited-by-visitors"></a>

Everything the visitor sees is calculated in the browser so it updates instantly. **The browser's number is never trusted.** When the form is submitted, the server recalculates every smart variable and the price from the submitted fields, using the same formulas, tables, product and catalog overrides, page meta and user role. If the visitor's price doesn't match, the submission is refused with *Price verification failed. Please reload the page and try again.* If a formula can't be calculated on the server at all, the submission is refused too, rather than accepting the browser's number.

### Troubleshooting <a href="#troubleshooting" id="troubleshooting"></a>

| Problem                                    | Check                                                                                                                                                                                                                                            |
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| The variable is always its default         | A field it uses is empty, or the name is misspelled. Copy names from the **Reference** column.                                                                                                                                                   |
| A table variable stays at the default      | With **Exact match**, the value must equal a key exactly (`50` is not `50 cm`). With **Closest**, the keys must be numbers, and an empty or 0 value doesn't look anything up.                                                                    |
| `{{field.name}}` shows nothing             | Turn on **Expose as field variable**.                                                                                                                                                                                                            |
| The value isn't in the submission or email | Turn on **Expose** and set a **Submission label**.                                                                                                                                                                                               |
| The price is wildly wrong with `^`         | Use `**` for powers.                                                                                                                                                                                                                             |
| A User role rule never applies             | Is the role chip red (*not registered*)? Does an earlier rule match first? For an Administrator testing the form, remember administrators match rules for their own role, not for the role you're testing. Log in as a test user with that role. |
| A wholesale customer sees the guest price  | A page cache is serving cached pages to logged-in users. Exclude the page from caching for logged-in users.                                                                                                                                      |

<br>
