Skip to content

Formulas

Formulas let a property compute its value automatically on every save, based on data from the same entity, its parents, its children, or entities that reference it.

To use a formula: set the formula flag on a property definition and write the expression as the formula value. Computed properties cannot be edited manually and are skipped when duplicating an entity.

Formulas are evaluated in two passes so that properties depending on other formula properties resolve correctly.

INFO

Two-pass evaluation means a formula can safely reference another computed property on the same entity. The first pass resolves simple fields; the second resolves dependencies between computed properties.

Syntax

Formulas use Reverse Polish Notation (RPN, also known as postfix notation): values come first, the operator comes last. The engine walks the formula left to right with a single value stack. Each token either:

  • Pushes a value onto the stack (a literal, a field reference), or
  • Runs an operator that pops values from the stack and pushes a result back.
first_name " " last_name CONCAT
│           │   │         └── pop the whole stack, join the values, push the joined string
│           │   └── push the value of `last_name`
│           └── push the string literal " "
└── push the value of `first_name`

There are no parentheses and no brackets in the language. Composition happens naturally on the stack — each operator produces one value that the next operator consumes.

Implicit CONCAT

If a formula does not end with a recognized operator keyword, the engine implicitly appends CONCAT and joins everything on the stack as a string. This makes simple string compositions short to write:

first_name " " last_name                  → "John Doe"          (implicit CONCAT)
first_name " " last_name CONCAT           → "John Doe"          (explicit, same result)
'Nr ' vr_nr '. ' otsuse_kp                → "Nr 12. 2026-01-01"

Operator categories

Every operator falls into one of these classes:

  • Variadic reducers consume the entire current stack and push one result. Most aggregating operators are reducers — CONCAT, SUM, MIN, IN, etc.
  • Fixed-arity operators pop a known number of stack slots. EQ pops 2, ABS pops 1, IF pops 3, and so on.
  • Per-value operators (ABS, ROUND) take one input slot and apply the operation to every underlying value in it, pushing back a slot of the same length.

Multi-value (list) properties

Entu properties are multi-value: a single field reference may push a list of N values as one stack slot. Operators handle lists like this:

  • Variadic reducers flatten popped slots into a single value sequence before applying the operation. _child.*.price SUM is just a list slot being reduced — the underlying values are summed.
  • Per-value operators apply to every value in their input slot — _child.*.delta ABS returns a list of absolute deltas, same length as the input.
  • Binary comparisons (EQ, GT, …) use ANY semantics across the cross product: true if any left value satisfies the operator against any right value.

Field References

Same Entity

ReferenceResolves to
propertyNameValue(s) of that property on the current entity
_idThe current entity's ID
'literal' or "literal"A string literal
123 / 45.67 / -2A numeric literal
true / falseA boolean literal

Referenced Entities

ReferenceResolves to
propertyName.*.propertyProperty value from all entities referenced by propertyName
propertyName.type.propertyProperty value from referenced entities filtered by entity type
propertyName.*._idIDs of all referenced entities
propertyName.type._idIDs of referenced entities filtered by type

Child Entities

ReferenceResolves to
_child.*.propertyNamepropertyName from all child entities
_child.typeName.propertyNamepropertyName from child entities of a specific type
_child.*._idIDs of all child entities
_child.typeName._idIDs of child entities of a specific type

Referrer Entities

Entities that reference this entity through their own reference properties:

ReferenceResolves to
_referrer.*.propertyNamepropertyName from all entities that reference this entity
_referrer.typeName.propertyNamepropertyName from referrers of a specific type
_referrer.*._idIDs of all referrer entities
_referrer.typeName._idIDs of referrer entities of a specific type

A referrer is an entity that points at the current entity through a user-defined reference-type property. System reference properties (_parent, _owner, _editor, _viewer, _expander) are not counted by _referrer.

INFO

typeName is matched against the referrer entity type's name property (e.g. invoice), not its display label. If a type's name and label differ, use the name value.

INFO

Unlike same-entity formulas, a _referrer formula depends on other entities. Its value updates when a referencing entity is created, changed, deleted, or has its reference changed — Entu then queues the target entity for automatic re-aggregation so its _referrer (and _child) formulas recompute. The result is eventually consistent: it may not be updated within the same request that changed the referencing entity.

Operators

Variadic reducers (consume the whole stack)

OperatorResultNotes
CONCATstringFlatten all stack values, stringify, join with no separator.
CONCAT_WSstringFlatten all stack values; the last value is the separator; the rest are joined with it.
SUMnumberStrict numeric — any non-number anywhere → no value.
SUBTRACTnumberLeft-to-right: first value minus the rest. Strict numeric.
MULTIPLYnumberProduct of all values. Strict numeric.
DIVIDEnumberFirst value divided by the rest, in order. Strict numeric. Division by zero → no value.
COUNTnumberTotal count of all underlying values. Empty stack → 0.
AVERAGEnumberArithmetic mean. Strict numeric.
MIN, MAXnumber or stringCompare with < / >. All values must be the same primitive type (all numbers or all strings — ISO 8601 dates compare correctly as strings).
IN, NINbooleanFirst slot = needle, remaining slots = haystack. IN is true if any needle value strict-equals any haystack value; NIN is the negation.

Binary comparisons (return boolean)

OperatorBehavior
EQ, NEStrict === / !== across the cross product.
GT, GTE, LT, LTEOrdering across the cross product. Both sides must be numbers or both strings — pairs of incompatible types are skipped.

All six use ANY semantics: true if any left value satisfies the operator against any right value.

Conditional

OperatorArityBehavior
IF3Pops else, then, cond. Returns then if cond is true, else if false.
WHEN2Pops then, cond. Returns then if cond is true; otherwise no property is written.

Both require cond to resolve to exactly one boolean value. Both branches are evaluated before the conditional runs (no lazy evaluation).

Per-value

OperatorArityBehavior
ABS1Absolute value of every number in the input slot.
ROUND2Pops decimals (a single number) and value. Rounds every number in value to decimals decimal places.

Other

OperatorArityBehavior
EXISTS1True if the input slot resolves to at least one value.

Empty Input Behaviour

Most operators return no value (the property is not written) when their inputs resolve to nothing:

OperatorEmpty input
COUNT0
CONCAT, CONCAT_WS, SUM, SUBTRACT, MULTIPLY, DIVIDE, AVERAGE, MIN, MAXno value (property not written)
ABS, ROUNDno value
IN, NINempty needle or empty haystack → false / true respectively
EQ, NE, GT, GTE, LT, LTEempty side → no value
EXISTSalways returns a boolean
IF, WHENno value if cond is empty or not a single boolean; WHEN returns no value when cond is false

Examples

Aggregations

Sum across children:

_child.*.price SUM

Count children of a specific type:

_child.invoice._id COUNT

Average price:

_child.*.price AVERAGE

Earliest due date:

_child.*.due_date MIN

Arithmetic

Profit:

income expenses SUBTRACT

Total with tax:

price tax SUM quantity MULTIPLY

Round to 2 decimals:

total quantity DIVIDE 2 ROUND

Absolute difference, rounded:

sum ABS invoice_sum ABS SUBTRACT 2 ROUND

Strings

Full name (implicit CONCAT):

first_name " " last_name

Full name with explicit CONCAT_WS:

first_name last_name " " CONCAT_WS

Two-level join — list of artists joined with ", ", then prefixed to a title:

artist ", " CONCAT_WS title " - " CONCAT_WS

Conditionals

Label by price threshold:

price 100 GT "expensive" "cheap" IF

Flag only when over budget (no else):

total budget GT "over budget" WHEN

Membership check with inline list:

status_code 10 20 30 IN "active" "inactive" IF

Mark whether a date is set:

paid_date EXISTS "✓" "—" IF

Overdue check by date:

due_date "2026-01-01" LT "overdue" "ok" IF

Exclude banned statuses (field as item list):

status banned_status.*.code NIN "ok" "blocked" IF

Composition rules

Because variadic reducers consume the entire stack, a formula can practically contain only one reducer before fixed-arity operations. Once a reducer runs, anything pushed after it sits on top of its result — and the next reducer will swallow both.

If you need two independent reducer results combined together (e.g. a count and a sum each rendered to a string), split the calculation into two formula properties: define one property whose formula produces the count, another whose formula produces the sum, and a third whose formula references both. The two-pass evaluator resolves the dependency.

Fixed-arity operators (EQ, GT, IN, IF, ROUND, ABS, …) can be chained freely.