In formulas
+ - * / % ^- Add, subtract, multiply, divide, remainder and power. Parentheses group as usual.
abs(x)- The value without its sign — abs(-3) is 3.
ceil(x)- Rounds up to the next whole number — ceil(2.1) is 3.
floor(x)- Rounds down to the previous whole number — floor(2.9) is 2.
round(x, digits)- Rounds to the nearest whole number — or to a number of decimal places: round(2.345, 2) is 2.35.
min(a, b, …)- The smallest of any number of values.
max(a, b, …)- The largest of any number of values.
How formulas behave
- References are field keys and must point at other number fields. Inside a repeating group, a row also sees the template’s top-level number fields.
- A missing, empty or non-numeric input makes the result empty — never zero — and an empty result carries through formulas that build on it.
- Division by zero gives an empty result rather than an error.
- The final value rounds to the field’s decimal places.
- Unknown references and circular formulas are rejected as you type; a formula caps at 1024 characters.
Totals over repeating groups
A formula can also reduce a repeating group’s rows to a single number. Name the group, then the sub-field column — over a line-items group items with a number sub-field cost, an order total is:
order_total = sum(items, cost)
The total adds up the cost of every row and updates as rows are added and filled in.
sum(group, field)- The total of a number column over the group’s rows.
avg(group, field)- The average of the filled cells in a number column.
min(group, field)- The smallest value in a number column — the same min, aggregating when its first argument is a group.
max(group, field)- The largest value in a number column, the same way.
count(group)- How many rows the group has.
countFilled(group, field)- How many rows have answered that column.
defined(group, field)- 1 when every row has answered that column, 0 otherwise.
sum(group.nested, field)- Every aggregation also takes a dotted path into nested groups: sum(items.item, cost) totals the cost column of every item row across all items rows, flattened into one set.
How aggregation behaves
- Blank or non-numeric cells are skipped, not counted as zero — a total stays partial and grows as rows are filled in. Zero and negative values do count.
- countFilled and defined ask whether a cell is answered: empty text and an empty selection are not answers, while an unticked checkbox (false) and the number 0 are.
- Aggregation starts at the direct child groups of the formula’s own scope and reaches deeper with dots: sum(items.item, cost) gathers every item row of every items row into one flat set. A row formula can do the same over the row’s own nested groups — never over the group it sits in. A row that is missing the nested list simply contributes nothing to the total.
- Over an empty group, sum, count and countFilled give 0; avg, min and max give an empty result; defined gives 1 — with no rows, nothing is left unanswered.
In conditions
== !=- Equal and not equal. Exact match, no type bending — the number 1 is not the text "1".
> >= < <=- Number comparisons. False whenever either side is not a number.
contains(field, value)- On a multi-select, true when that choice is picked; on text, true when the text contains the fragment.
isEmpty(field)- True for empty text or when nothing is selected. An unchecked checkbox and the number 0 are not empty.
isNotEmpty(field)- The opposite of isEmpty.
and or- Chain conditions with and, or with or — one rule uses one of them, never both.
How conditions behave
- The field key goes on the left, the value on the right: a number, text in double quotes, or true and false. Comparing two fields to each other is not supported.
- A condition over a missing value is false, so a show-rule hides its field — rules fail towards hiding, never towards showing by accident.
- The same conditions power field visibility, card visibility and workflow advance guards.
Conditions over repeating groups
A condition can also ask about a repeating group as a whole — how many rows it has, or whether every row has answered a column. Name the group — or a dotted path into its nested groups — then the column. These are the three forms:
defined(items, mat)count(items) > 0countFilled(items, note) >= 2
defined(group, field)- True when that column is answered in every row.
count(group) > 0- How many rows the group has, compared against a plain number. Every comparison operator works: == != > >= < <=.
countFilled(group, field) >= 2- How many rows have answered that column, compared against a plain number.
How group conditions behave
- defined(group, field) is true when that field is filled in every row — and also when the group has no rows at all. To require rows as well, write count(group) > 0 and defined(group, field). It is the same vacuous truth the formula defined has over an empty group, and it is the one caveat that quietly lets an empty order through a gate. It holds through a dotted path too: a row whose nested list is empty leaves defined true.
- Answered means the same here as in the aggregation functions: empty text and an empty selection are not answers, while an unchecked checkbox (false) and the number 0 are.
- A group condition names a top-level repeating group and reaches its nested groups with dots: defined(items.item, mat) asks about every item row of every items row, flattened into one set — count(items.item) > 0 counts them all.
- If the group is not there at all — hidden by a card rule, or never filled in — every condition about it is false, including count(group) == 0. The same holds at depth: if any row is missing its nested list entirely, the condition is false — a gate never opens on malformed data. Formulas differ here on purpose: an aggregation quietly skips such a row, a condition refuses.
- All three forms work everywhere a condition does: field visibility, card visibility and workflow advance guards.
Worked example: never deduct on a half-filled order
A line-items group items holds a material reference and a quantity column qty bound to stock deduction. On the stage before the one that deducts, set the advance condition to:
count(items) > 0 and defined(items, qty)
An order with no rows cannot advance, and neither can one whose rows still have a blank quantity — so stock never leaves for an order nobody finished filling in.
This gate is the moment to get it right: once an order has passed the deduction stage, the amounts it deducted are locked. Editing them is refused, because that stock has already left the shelf.