Expressions in Nodlin

Sep 5, 2026 ยท 6 min read

What are expressions?

Expression nodes compute a single value from connected graph data โ€” similar to a formula cell in a spreadsheet.

They are not full Nodlin scripts. Scripts (see Python scripting) define custom types, forms, images, and multi-step logic. Expressions are a separate, lighter interpreter for one-line Starlark formulas.

Expression nodeScript node
PurposeCalculate a resultCustom behaviour, UI, side effects
LengthSingle formula lineFull script
AccessAliases, related(), col(), aggregatesnode, context, factory, forms, links
ResultNumber, condition, list, or mapFull node payload + form + image

When to use expressions

Use expressions when you need:

  • Totals, averages, maxima over linked values
  • Simple conditions (revenue > cost)
  • A column from a table on a spreadsheet or script payload
  • A named intermediate value other nodes can reference

Use scripts when you need forms, custom visuals, creating nodes/links, or multi-step workflows.


Mental model (Excel โ†’ Nodlin)

ExcelNodlin expression
=A1+B1price + quantity (node aliases)
=SUM(A1:A10)sum(related()) or sum(col(table, 1))
=AVERAGE(...)avg(...)
=MAX(...) / =MIN(...)max(...) / min(...)
=IF(A1>10,"high","low")"high" if a > 10 else "low"

Key difference: instead of cell addresses, you use aliases on connected nodes and/or the related() function over relationships.


Quick start

1. Alias connected inputs

Give related nodes short aliases (e.g. price, qty, trial). In expressions those names become variables.

(price + tax) * qty
sum(related("value", "hasCost"))

3. Max age from a table on a spreadsheet

If a spreadsheet node is aliased trial and its value contains people as rows [["fred", 23], ...]:

max(col(trial["people"], 1))

4. Condition

revenue > cost

Result displays as TRUE / FALSE on the node.


Aliases

Each connected inbound node with an alias exposes its default binding as a variable:

  1. Prefer the value property
  2. Else condition
  3. Else the whole payload (dict)
# Scalar values
price * qty

# Dict binding (e.g. spreadsheet value map)
trial["cost"]
trial["people"]

Tips

  • Aliases must be unique among inbound related nodes used by the expression.
  • Prefer aliases over related()[0] when there is a single clear source node.
  • Nested paths use Starlark subscripts: trial["a"]["b"], rows[0].

related() reads properties from inbound connected nodes and always returns a list.

Syntax

related([prop,] [relation,] [type])
ArgumentMeaningExample
propProperty to extract"value", "condition", "cost"
relationRelationship filter"hasCost", "input"
typeNode type filter (requires relation)"Product", "spreadsheetFile"

Names can be short FQN references in lowerCamel (e.g. hasCost, spreadsheetFile) or matching user labels.

Examples

related()
# โ†’ default value/condition from all inbound related nodes

related("value", "hasCost")
# โ†’ value props over hasCost

related("value", "owns", "Product")
# โ†’ Product values over owns

sum(related("value", "hasCost"))
avg(related())
max(related("value", "hasPrice"))

Exactly one match: hasOne

When you need exactly one related value (not a list), wrap with hasOne:

hasOne(related("value", "input", "spreadsheetFile"))
# โ†’ single value dict; errors if 0 or more than one match

This mirrors script-side node.R.<rel>.hasOne() cardinality checks. Prefer an alias when the source is named; use hasOne(related(...)) for the unaliased case instead of a bare [0] index.

# Preferred when aliased
max(col(trial["people"], 1))

# Unaliased edge case
max(col(hasOne(related("value", "input", "spreadsheetFile"))["people"], 1))

Tables: col() and aggregates

col(rows, index_or_name)

Extract one column from a list of rows:

Row shapeSecond argumentExample
List of lists / tuplesInteger index (0-based)col(trial["people"], 1)
List of dictsString keycol(trial["people"], "age")
# people = [["fred", 23], ["john", 26], ["peter", 92]]
max(col(trial["people"], 1))   # โ†’ 92
sum(col(trial["people"], 1))
avg(col(trial["people"], 1))
min(col(trial["people"], 1))

Missing keys or out-of-range indexes are errors (so mistakes are visible).

List comprehensions (escape hatch)

Still valid when you need custom logic:

max([age for name, age in trial["people"]])
max([row[1] for row in trial["people"]])
sum([x for x in related() if x > 100])

For plain column extract + aggregate, col is clearer.

Empty lists

max([]) and min([]) fail. Ensure the source list is non-empty, or handle emptiness upstream.


Operators and conditions

Arithmetic and comparison

+ - * / ^ ยท == != < > <= >= ยท and or not

(price + tax) * qty
revenue > cost and status == "approved"

Conditional expression (ternary)

Use the expression form, not an if-statement:

# Valid
"high" if a > 10 else "low"
A1 if B2 > 0 else B2 + A1

# Invalid (statement โ€” not allowed in a single expression)
# if B2 > 0: A1 else: B2 + A1

Parentheses and strings

(price + tax) * qty
"hello"
'world'

Built-in functions (summary)

Nodlin-specific

FunctionReturnsRole
related([prop,] [relation,] [type])ListValues from inbound related nodes
sum(list)NumberSum numerics (non-numerics ignored)
avg(list)NumberAverage numerics
col(rows, index|name)ListColumn from list-of-rows
hasOne(list)AnyExactly one list element

Starlark (also available)

max, min, len, abs, sorted, enumerate, zip, any, all, int, float, str, bool, list, dict, type, โ€ฆ

Modules

math.sqrt(16)
math.pi
math.ceil(3.2)

time.now()
# (time helpers as provided by the expression environment)

Result types and display

An expression evaluates to one of:

TypeExampleOn the node
Numberprice * qtyFormatted value (scale, currency, %)
Conditionrevenue > costTRUE / FALSE (or icons)
Listrelated() / col(...)Compact list summary
Mapdict resultKey summary

Formatting (on the expression node, not in the formula)

SettingPurpose
ScaleDecimal places (0โ€“8)
Currency / PCTe.g. USD, EUR, or percentage
Size / coloursNode appearance
Show labelTitle vs comment
List icon optionsFirst / last / both for list results

Errors surface with a clear error state on the node (and in the form) so formula mistakes are visible.


Worked examples

Totals and tax

Aliases: product = 29.99, taxRate = 0.08, qty = 3

(product + product * taxRate) * qty
# โ†’ 97.1694  (format as USD, scale 2 โ†’ $97.17)

Average cost over a relationship

avg(related("value", "hasCost"))
# connected costs [10, 15, 25] โ†’ 16.67

Profit check

(product * qty) > sum(related("value", "hasCost"))
# โ†’ True

Spreadsheet table column

Spreadsheet alias trial, value.people = name/age rows:

max(col(trial["people"], 1))
# โ†’ 92
max(col(hasOne(related("value", "input", "spreadsheetFile"))["people"], 1))

Writing expressions in the UI

Expression fields use a CodeMirror editor (Python highlighting) with an expandable helper panel:

  1. Named inputs โ€” insert aliases; open Fieldsโ€ฆ for dict paths
  2. Aggregate column โ€” for list-of-rows fields, pick column + max/min/sum/avg โ†’ inserts max(col(...))
  3. Related list โ€” build related(...) / sum(related(...)) / hasOne(related(...))
  4. Operators โ€” chips and templates (col, comprehensions, conditionals)

Habit that pays off: alias the source node, use Fieldsโ€ฆ + Aggregate column, avoid hand-written related()[0] when hasOne or an alias will do.

Forms can also embed the same control via webform.addExpressionEditor โ€” see Forms.


Expressions vs scripts (checklist)

NeedPrefer
One calculated number / condition / listExpression
Reuse that value by name on neighboursExpression + alias
Custom form, SVG, create/link nodesScript
Multi-step workflow / actionsScript
Domain-specific node typeScript package

Troubleshooting

SymptomLikely cause
Unknown nameMissing alias, or node not inbound-related
hasOne(): no items / >1 itemsZero or multiple matches โ€” fix links or filter relation/type
col(): โ€ฆ out of range / key not foundWrong column index/name or ragged rows
max/min on empty listNo rows โ€” check source path
Invalid if/elseUse a if cond else b, not if cond: โ€ฆ
Type without relationrelated requires relation whenever type is set

Further reading

  • Implementation-oriented language notes live with the expression agent in the server repo (NODLIN_EXPRESSION_LANGUAGE_SPEC.md).
  • Script API (including relationship hasOne on nodes): Starlark extensions
  • Starlark spec
John Harrington
Authors
John Harrington
Founder
A technology leader and software architect with 20+ years in financial services and enterprise systems, founder of Nodlin Technologies Ltd๏ฟผ, building connected AI-driven operational intelligence platforms.