Skip to content

Cross-row aggregates

A repeater’s value is a plain array of row objects. That means a form-level expression can reference the whole table with %fieldName% and hand it to an array-aware function:

sum(%directors%, 'sharePct')

No new syntax — these are ordinary function calls, dispatched through the expression function registry.

CallReturns
sum(rows, 'col')Total of the numeric values in col.
count(rows)Number of rows.
count(rows, 'col')Rows where col is filled.
min(rows, 'col')Smallest numeric value, or null if there are none.
max(rows, 'col')Largest numeric value, or null if there are none.
avg(rows, 'col')Mean of the numeric values, or null if there are none.

The column argument is optional. Omit it when the array holds primitives rather than row objects: sum(%scores%).

These run on every keystroke, against forms that are mostly empty most of the time, so the edge cases are deliberate:

  • Non-numeric and empty cells contribute 0 to sum. One half-typed row must not turn a running total into NaN.
  • min, max and avg return null when no numeric values exist — not 0, which would be indistinguishable from a real zero in the data.
  • A non-array aggregates to the empty case rather than throwing. A repeater that hasn’t been seeded yet, or was cleared to '', is not an error.
  • count(rows, 'col') skips empty strings, null, undefined and empty arrays — “filled”, not merely “present”.

The common case: a total that follows the rows.

Cap table with a live totalOpen full playground

Editing a row changes the array’s identity, which the calculated-value graph sees as a dependency change — so the total settles synchronously, in the same write. See calculated values.

Aggregates aren’t limited to calculateValue. They work anywhere an expression does, which is how you express whole-table rules:

{
"name": "overAllocated",
"type": "error",
"required": "sum(%directors%, 'sharePct') > 100",
"validation": [
{ "name": "required", "message": "Shares add up to more than 100%." }
]
}

Open in playground

An error field holds no value of its own — it exists to carry a group rule and render the message when it fails. Pairing an aggregate required expression with a required validator gives you “these rows must add up” without writing a custom validator.

The same works for visibility:

{
"name": "bulkDiscountNote",
"type": "text",
"label": "Bulk discount applied",
"show": "count(%items%) >= 10"
}

Open in playground

median, stddev, a weighted average — register it once and it’s callable from any expression. See expression functions.