Skip to content

Calculated field expressions

A calculated field is a stored scalar field whose value is produced by an expression instead of user input. The value is persisted; runtime writes ignore user-supplied values for calculated targets. You configure one expression per field, and the server infers whether it is a local calculation (same record) or an aggregate calculation (over child records).

Supported target field types: Integer, Decimal, and Money.

These are separate contracts:

RequirementUse
Extra read-time text in Table and Card surfaces, such as a Full name value rendered from {LastName} {FirstName}A Display field. It adds no stored column and cannot be searched, filtered, sorted, edited, or used by business rules.
A stored value produced by a supported formulaA calculated field described on this page. The runtime owns and persists the target value.
A stored derivation that needs procedural logic or additional readsA C# before-save mutation function. Backfilling existing records is a separate operation unless the C# workflow implements it.

Display name and subtitle are presentation rules, not Display Fields or stored calculations.

A local expression runs on one record and may reference fields from the same entity table only — no dotted reference paths.

Building blocks:

  • numeric literals (12.50), parentheses, unary +/-;
  • arithmetic: +, -, *, /;
  • same-table field keys: Qty, Price, Discount;
  • functions: IF, COALESCE, ROUND, ABS, MIN, MAX (see the function reference).
Qty * Price
Total * (1 - Discount / 100)
IF(Qty > 0, Qty, 0)
ROUND(Total * (1 - Discount / 100), 2)
COALESCE(ManualTotal, Total)

IF conditions use a deliberately small grammar: same-table numeric, money, and boolean fields, numeric literals, TRUE/FALSE/NULL, comparisons, AND/OR/NOT, and IS [NOT] NULL. Text operators, dates, references, and child-table forms are not available inside calculation conditions.

Typing is strict: numeric combines with numeric, money with money, and one IF branch may be NULL. Mixing money with plain numbers in one result is invalid. Integer targets truncate decimal results on write.

Money arithmetic keeps the source currency. Supported: money ± money, money × or ÷ number, number × money. Combining or comparing money values with different currency codes makes the calculation invalid for that record rather than silently converting.

An aggregate expression runs from a parent record over its child records:

SUM(Positions, TotalPrice)
COUNT(Positions, Id)
  • The first argument is an inverse-reference field on the parent entity, paired with a Reference field on the child that points back to the parent.
  • The second argument is a primary-table field on that child definition.
  • SUM, AVG, MIN, MAX accept Integer, Decimal, and Money sources; COUNT with the child Id counts active rows, with another field it counts rows where that field is not null.
  • Archived child rows are excluded.
  • Empty set: SUM and COUNT return 0; AVG, MIN, MAX return NULL.
  • Money aggregates include only rows whose currency matches the result currency (the target field’s base currency, or the source currency when the target has none); any non-null row in another currency makes the result NULL instead of mixing currencies.

Not supported: filtered aggregates (SUM(Positions, Amount WHERE …)), path syntax (SUM(Positions[Order].TotalPrice)), collection paths outside aggregate functions, and arbitrary code.

Within one save the runtime applies, in order: mutation effects → local calculated fields → persist → aggregate recalculation on affected parents → local calculated fields on those parents. This supports chains such as:

OrderPosition.Price <- mutation effect copies Item.Price
OrderPosition.TotalPrice <- Qty * Price
Order.Total <- SUM(Positions, TotalPrice)
Order.TotalWithDiscount <- Total * (1 - Discount / 100)

Form previews show local calculated values as you edit; aggregate values are server truth after save.

For an API-first workflow, use the Configuration API served by the target installation:

  1. Read the current field, including CalculationExpression, CalculationKind, and RowVersion: GET /api/workspace/admin/entity-fields/{fieldId}.
  2. Validate an expression without saving: POST /api/workspace/admin/entity-fields/{fieldId}/calculation/validate with {"Expression":"Qty * Price"}.
  3. After validation succeeds, save exactly that expression with the current row version: PUT /api/workspace/admin/entity-fields/{fieldId}/calculation with {"Expression":"Qty * Price","RowVersion":"<current-guid>"}.

The generated calculation validate operation — errors include the request field, localized message, and stable code; a valid result also reports the inferred calculation kind (local or aggregate). Then set or change the expression through the field’s calculation operation in the Configuration API. Invalid expressions are rejected with the same structured errors. Fields referenced by a calculation become schema dependencies: they block deletion and incompatible type changes until the calculation is updated.