Calculated Field
Derive a value from other form fields with a small formula language — totals, taxes, durations, conditional labels, and table aggregates
What This Feature Does
A Calculated field doesn't ask the form filler for input — instead, the form works out its value from the answers already given in other fields. As the filler types, the Calculated field updates live.
Typical examples:
- Line total =
{quantity} * {unitPrice} - Tax =
{subtotal} * 0.1 - Status label =
IF({age} >= 18, "Adult", "Minor") - Job duration =
{endTime} - {startTime} - Subtotal over a table =
SUM({lineItems.amount})
The value the form filler sees is the same value the server stores — every Calculated field is re-computed server-side at submit time, so the persisted scalar is always the formula's truth.
When to Use a Calculated Field
- Line totals on a quote, invoice, or purchase order
- Subtotals over a Table field (materials list, line items, daily timesheet rows)
- Tax / GST applied to a subtotal
- Derived labels ("Adult / Minor", "Pass / Fail", "On-track / Late")
- Durations between two times or dates
- Conditional discounts ("10% off if more than 5 items")
- Any number the field crew would otherwise reach for a calculator to work out
If the value should come from outside the form (a record in another module, last week's submission, a database lookup), use a Resource field or a Workflow instead — Calculated fields only see data the same submission has captured.
Adding a Calculated Field
- Open your form in the Form Builder
- From the field palette, click the Calculator icon and drop it onto your form (or drag-and-drop like any other field)
- Give it a Label ("Subtotal", "Tax", "Total") and a Name (the internal identifier other formulas use to refer to it)
- Pick an Output format — Number, Currency, Duration, Text, or Yes/No (see Output formats)
- In the Formula box, use the Visual or Advanced builder to compose the expression
- Watch the Live preview under the formula — it shows what the formula will produce using each referenced field's default value
- Save the form
Visual vs Advanced Builder
The Calculated field has two authoring modes, toggled at the top of the formula builder.
| Mode | When it suits | What it looks like |
|---|---|---|
| Visual | Composing a new formula on a desktop, learning the syntax | Chip-based — pick a field, drop in an operator chip, pick a function, drop in a number. Drag chips to reorder. |
| Advanced | Editing an existing formula, working on a phone, copy-pasting a formula from a sibling form | Plain-text editor with auto-paired brackets and field-name autocomplete. |
On a phone, Advanced is the default — chip drag-and-drop on a 5-inch screen is too cramped. You can switch at any time without losing your work — both modes read from and write to the same formula string.
If you're in Visual mode and an Advanced-mode formula contains syntax the chips can't represent (e.g. a deeply nested conditional), Visual mode shows a "switch to Advanced to edit" notice rather than silently dropping the syntax.
Formula Syntax Basics
Formulas are written using friendly operator names and the {fieldName} reference syntax.
Arithmetic
+add —{a} + {b}-subtract —{a} - {b}*multiply —{quantity} * {unitPrice}/divide —{total} / {count}
Comparison
| In the chip builder | In Advanced mode | Means |
|---|---|---|
| EQ | == | equals |
| NEQ | != | not equal |
| GT | > | greater than |
| GTE | >= | greater than or equal |
| LT | < | less than |
| LTE | <= | less than or equal |
Logical
| In the chip builder | In Advanced mode | Means |
|---|---|---|
| AND | && | both conditions true |
| OR | || | either condition true |
| NOT | ! | invert the condition |
Conditional
Use the IF(condition, then, else) function:
IF({hoursWorked} > 8, "Overtime", "Regular")
Referencing other fields
Wrap the field's name (not its label) in braces:
{startTime}— a scalar field{lineItems.qty}— theqtycolumn of thelineItemsTable (see Working with Table fields)
The Advanced editor offers field-name autocomplete: type { and a dropdown appears with every field you can reference.
You cannot reference:
- The Calculated field itself (no self-reference)
- A Signature, File Upload, or Map field (those values aren't numbers)
- A field that doesn't exist yet — save errors will flag it
Output Formats
The output format controls how the result is shown on screen and how it's stored.
| Format | Example formula | Example result | Notes |
|---|---|---|---|
| Number | {quantity} * {unitPrice} | 42.50 | Choose precision (0–10 decimal places). |
| Currency | {subtotal} * 1.1 | $1,099.00 | Choose symbol (e.g. $, €, £, A$) and position (prefix or suffix). |
| Duration | ({endTime} - {startTime}) | 8h 30m | The formula must yield a number in seconds; Times are auto-converted (HH:MM → decimal hours, then displayed back). |
| Text | IF({status} == "ok", "Pass", "Fail") | Pass | The only format where you can return a string from IF. |
| Yes/No | {age} >= 18 | Yes | true / 1 → "Yes", false / 0 → "No". |
Functions
Eight named functions are available. Names are UPPERCASE.
| Function | What it does | Example |
|---|---|---|
SUM(...) | Add every numeric value | SUM({lineItems.amount}) totals every row's amount |
AVG(...) | Average of every numeric value | AVG({ratings.score}) — average score |
COUNT(...) | Count of numeric, non-empty values | COUNT({lineItems.qty}) — how many rows have a qty |
MIN(...) | Smallest value | MIN({prices.amount}) |
MAX(...) | Largest value | MAX({prices.amount}) |
ROUND(x, decimals) | Round to N decimal places | ROUND({subtotal} * 0.1, 2) — tax rounded to cents |
ABS(x) | Absolute value (drop the minus sign) | ABS({delta}) |
IF(cond, then, else) | Branch on a condition | IF({age} >= 18, "Adult", "Minor") |
Aggregates (SUM/AVG/COUNT/MIN/MAX) accept either a list of arguments or a Table column:
SUM(1, 2, 3) → 6
SUM({lineItems.qty}) → sum of every row's qty
SUM({lineItems.qty}, 5) → sum of every row's qty + 5
Empty aggregates return null for AVG/MIN/MAX (the field shows "—"), and 0 for SUM/COUNT.
Working with Table Fields
To pull a column out of a Table (or Material Transaction or Equipment Time) field, use dot syntax: {tableName.columnName}.
A Table called lineItems with columns qty, unitPrice, and description exposes:
{lineItems.qty}— the array of every row'sqty{lineItems.unitPrice}— the array of every row'sunitPrice
Wrap a column reference in an aggregate to get a single number:
SUM({lineItems.qty} * {lineItems.unitPrice}) ← this does NOT work yet
SUM({lineItems.lineTotal}) ← this DOES work
if lineTotal is a column
Per-cell formulas inside Table rows aren't supported yet. The workaround is to add a lineTotal column to the Table whose values the form filler enters (or, more usually, that another Calculated field outside the table sums).
Hidden / conditionally-hidden rows are skipped from aggregates. If a row has __rowVisible: false (set automatically by row-level conditional logic), it's not counted.
Conditional Visibility
A Calculated field can be hidden or shown based on other field values, just like any other field. Open the field's Conditional section in the configuration panel.
A hidden Calculated field still computes its value behind the scenes. That means:
- Workflows that read the submission still see the value
- Other Calculated fields that depend on it still get the right number
- The PDF export decides whether to show it based on the same visibility rule
If you want a Calculated field to genuinely not compute (e.g. because referenced fields aren't filled in yet), make sure those referenced fields are themselves blank or hidden — the formula treats missing references as zero.
Pitfalls
- Referencing a field that doesn't exist yet — the form will refuse to save with a red banner under the Calculated field. Fix: create the referenced field first, or correct the spelling. The field-name autocomplete in the Advanced editor helps avoid this.
- Renaming a referenced field — the formula will keep the old name and stop working. Open the Calculated field and re-pick the renamed field (or hand-edit the formula).
- Changing the output format on a published form — workflows downstream may be expecting the old type. A workflow that expected
"$1,099.00"(Currency) won't recognise1099(Number). - Division by zero — the field shows
"—"rather than crashing. If you want a friendlier fallback, wrap the formula inIF:IF({count} > 0, {total} / {count}, 0). - Cycles between Calculated fields —
total = subtotal + taxandtax = total * 0.1is a cycle. Save will refuse the form with a cycle error showing the chain (total → tax → total). - Self-reference — a Calculated field cannot reference its own name. The formula box will reject it.
- Editing the formula does not retro-update old submissions. Each submission stores the formula as it was at submit time. A re-published form's new formula only applies to new submissions. (Behind the scenes, the PDF export of an old submission uses the snapshot too.)
- Big numbers — formulas use standard JavaScript numbers. Currency totals above ~9 quadrillion will lose precision. Realistic invoices are fine.
For Developers / Advanced Users
The formula engine is a deliberately small expression language. Full developer documentation — threat model, the public API, how to add a new function, the lifecycle of a formula from edit to PDF — lives in the codebase:
packages/features/forms/src/form-builder/lib/formula/README.md
The dev-tool component catalog also has an interactive playground for the Calculated field's config drawer — search for Calculated Field in the dev-tool components page.
Frequently Asked Questions
Why doesn't total = {qty} * {price} work?
The = would be an assignment, which is disabled. A Calculated field's label is "Total"; the formula is just the right-hand side: {qty} * {price}.
How do I average just the visible rows of a Table?
Aggregates already skip hidden rows automatically. If you've added a row-level conditional to your Table, AVG({rows.col}) averages only the rows that condition shows.
Can I reference a Calculated field from another Calculated field?
Yes. The renderer and the server both walk Calculated fields in dependency order, so total = subtotal + tax and tax = subtotal * 0.1 work fine — subtotal is computed first, tax second, total last.
Why does my Duration field show "0s"?
Either the formula yielded 0 (start and end are the same), or it yielded a negative number — durations clamp to zero rather than show "−2h 30m". Check the order of your subtraction.
Can a Calculated field include text from another field?
Not by concatenation — there's no CONCAT function. The only string-producing operator is IF(cond, "yes", "no"). If you need fully dynamic text, use a Workflow with a template, not a Calculated field.
What happens if a referenced field is hidden?
The hidden field's value is treated as null — i.e. missing. In arithmetic, null propagates as null (so null + 5 is null, and the Calculated field shows "—"). In aggregates, null rows are skipped.
What's Next
- Add the field to a form via the Form Builder
- Combine with Conditional Logic to show derived values only when relevant
- Trigger a Workflow when a Calculated value crosses a threshold
- Reference Calculated values from the Template Builder to surface them on the generated PDF

