<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Expressions | nodlin.com</title><link>https://nodlin.com/tag/expressions/</link><atom:link href="https://nodlin.com/tag/expressions/index.xml" rel="self" type="application/rss+xml"/><description>Expressions</description><generator>Hugo Blox Builder (https://hugoblox.com)</generator><language>en-us</language><lastBuildDate>Sat, 05 Sep 2026 00:00:00 +0000</lastBuildDate><image><url>https://nodlin.com/media/logo.svg</url><title>Expressions</title><link>https://nodlin.com/tag/expressions/</link></image><item><title>Expressions in Nodlin</title><link>https://nodlin.com/platform/expression/</link><pubDate>Sat, 05 Sep 2026 00:00:00 +0000</pubDate><guid>https://nodlin.com/platform/expression/</guid><description>&lt;h2 id="what-are-expressions">What are expressions?&lt;/h2>
&lt;p>&lt;strong>Expression nodes&lt;/strong> compute a single value from connected graph data — similar to a formula cell in a spreadsheet.&lt;/p>
&lt;p>They are &lt;strong>not&lt;/strong> full Nodlin scripts. Scripts (see
) define custom types, forms, images, and multi-step logic. Expressions are a separate, lighter interpreter for &lt;strong>one-line Starlark formulas&lt;/strong>.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;strong>Expression node&lt;/strong>&lt;/th>
&lt;th>&lt;strong>Script node&lt;/strong>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Purpose&lt;/td>
&lt;td>Calculate a result&lt;/td>
&lt;td>Custom behaviour, UI, side effects&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Length&lt;/td>
&lt;td>Single formula line&lt;/td>
&lt;td>Full script&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Access&lt;/td>
&lt;td>Aliases, &lt;code>related()&lt;/code>, &lt;code>col()&lt;/code>, aggregates&lt;/td>
&lt;td>&lt;code>node&lt;/code>, &lt;code>context&lt;/code>, &lt;code>factory&lt;/code>, forms, links&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Result&lt;/td>
&lt;td>Number, condition, list, or map&lt;/td>
&lt;td>Full node payload + form + image&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="when-to-use-expressions">When to use expressions&lt;/h3>
&lt;p>Use &lt;strong>expressions&lt;/strong> when you need:&lt;/p>
&lt;ul>
&lt;li>Totals, averages, maxima over linked values&lt;/li>
&lt;li>Simple conditions (&lt;code>revenue &amp;gt; cost&lt;/code>)&lt;/li>
&lt;li>A column from a table on a spreadsheet or script payload&lt;/li>
&lt;li>A named intermediate value other nodes can reference&lt;/li>
&lt;/ul>
&lt;p>Use &lt;strong>scripts&lt;/strong> when you need forms, custom visuals, creating nodes/links, or multi-step workflows.&lt;/p>
&lt;h3 id="related-docs">Related docs&lt;/h3>
&lt;ul>
&lt;li>
— full Starlark scripts&lt;/li>
&lt;li>
— including the expression editor control&lt;/li>
&lt;li>
— language reference&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="mental-model-excel--nodlin">Mental model (Excel → Nodlin)&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Excel&lt;/th>
&lt;th>Nodlin expression&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>=A1+B1&lt;/code>&lt;/td>
&lt;td>&lt;code>price + quantity&lt;/code> (node &lt;strong>aliases&lt;/strong>)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>=SUM(A1:A10)&lt;/code>&lt;/td>
&lt;td>&lt;code>sum(related())&lt;/code> or &lt;code>sum(col(table, 1))&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>=AVERAGE(...)&lt;/code>&lt;/td>
&lt;td>&lt;code>avg(...)&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>=MAX(...)&lt;/code> / &lt;code>=MIN(...)&lt;/code>&lt;/td>
&lt;td>&lt;code>max(...)&lt;/code> / &lt;code>min(...)&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>=IF(A1&amp;gt;10,&amp;quot;high&amp;quot;,&amp;quot;low&amp;quot;)&lt;/code>&lt;/td>
&lt;td>&lt;code>&amp;quot;high&amp;quot; if a &amp;gt; 10 else &amp;quot;low&amp;quot;&lt;/code>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>Key difference:&lt;/strong> instead of cell addresses, you use &lt;strong>aliases&lt;/strong> on connected nodes and/or the &lt;code>related()&lt;/code> function over relationships.&lt;/p>
&lt;hr>
&lt;h2 id="quick-start">Quick start&lt;/h2>
&lt;h3 id="1-alias-connected-inputs">1. Alias connected inputs&lt;/h3>
&lt;p>Give related nodes short aliases (e.g. &lt;code>price&lt;/code>, &lt;code>qty&lt;/code>, &lt;code>trial&lt;/code>). In expressions those names become variables.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">(price + tax) * qty
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="2-sum-everything-on-a-link">2. Sum everything on a link&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">sum(related(&amp;#34;value&amp;#34;, &amp;#34;hasCost&amp;#34;))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="3-max-age-from-a-table-on-a-spreadsheet">3. Max age from a table on a spreadsheet&lt;/h3>
&lt;p>If a spreadsheet node is aliased &lt;code>trial&lt;/code> and its &lt;code>value&lt;/code> contains &lt;code>people&lt;/code> as rows &lt;code>[[&amp;quot;fred&amp;quot;, 23], ...]&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">max(col(trial[&amp;#34;people&amp;#34;], 1))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="4-condition">4. Condition&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">revenue &amp;gt; cost
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Result displays as &lt;strong>TRUE&lt;/strong> / &lt;strong>FALSE&lt;/strong> on the node.&lt;/p>
&lt;hr>
&lt;h2 id="aliases">Aliases&lt;/h2>
&lt;p>Each connected &lt;strong>inbound&lt;/strong> node with an alias exposes its default binding as a variable:&lt;/p>
&lt;ol>
&lt;li>Prefer the &lt;code>value&lt;/code> property&lt;/li>
&lt;li>Else &lt;code>condition&lt;/code>&lt;/li>
&lt;li>Else the whole payload (dict)&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl"># Scalar values
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">price * qty
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Dict binding (e.g. spreadsheet value map)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">trial[&amp;#34;cost&amp;#34;]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">trial[&amp;#34;people&amp;#34;]
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Tips&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Aliases must be unique among inbound related nodes used by the expression.&lt;/li>
&lt;li>Prefer aliases over &lt;code>related()[0]&lt;/code> when there is a single clear source node.&lt;/li>
&lt;li>Nested paths use Starlark subscripts: &lt;code>trial[&amp;quot;a&amp;quot;][&amp;quot;b&amp;quot;]&lt;/code>, &lt;code>rows[0]&lt;/code>.&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="the-related-function">The &lt;code>related()&lt;/code> function&lt;/h2>
&lt;p>&lt;code>related()&lt;/code> reads properties from &lt;strong>inbound&lt;/strong> connected nodes and always returns a &lt;strong>list&lt;/strong>.&lt;/p>
&lt;h3 id="syntax">Syntax&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">related([prop,] [relation,] [type])
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Argument&lt;/th>
&lt;th>Meaning&lt;/th>
&lt;th>Example&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>prop&lt;/code>&lt;/td>
&lt;td>Property to extract&lt;/td>
&lt;td>&lt;code>&amp;quot;value&amp;quot;&lt;/code>, &lt;code>&amp;quot;condition&amp;quot;&lt;/code>, &lt;code>&amp;quot;cost&amp;quot;&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>relation&lt;/code>&lt;/td>
&lt;td>Relationship filter&lt;/td>
&lt;td>&lt;code>&amp;quot;hasCost&amp;quot;&lt;/code>, &lt;code>&amp;quot;input&amp;quot;&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>type&lt;/code>&lt;/td>
&lt;td>Node type filter (requires &lt;code>relation&lt;/code>)&lt;/td>
&lt;td>&lt;code>&amp;quot;Product&amp;quot;&lt;/code>, &lt;code>&amp;quot;spreadsheetFile&amp;quot;&lt;/code>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Names can be &lt;strong>short FQN&lt;/strong> references in lowerCamel (e.g. &lt;code>hasCost&lt;/code>, &lt;code>spreadsheetFile&lt;/code>) or matching user labels.&lt;/p>
&lt;h3 id="examples">Examples&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">related()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># → default value/condition from all inbound related nodes
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">related(&amp;#34;value&amp;#34;, &amp;#34;hasCost&amp;#34;)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># → value props over hasCost
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">related(&amp;#34;value&amp;#34;, &amp;#34;owns&amp;#34;, &amp;#34;Product&amp;#34;)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># → Product values over owns
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sum(related(&amp;#34;value&amp;#34;, &amp;#34;hasCost&amp;#34;))
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">avg(related())
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">max(related(&amp;#34;value&amp;#34;, &amp;#34;hasPrice&amp;#34;))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="exactly-one-match-hasone">Exactly one match: &lt;code>hasOne&lt;/code>&lt;/h3>
&lt;p>When you need &lt;strong>exactly one&lt;/strong> related value (not a list), wrap with &lt;code>hasOne&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">hasOne(related(&amp;#34;value&amp;#34;, &amp;#34;input&amp;#34;, &amp;#34;spreadsheetFile&amp;#34;))
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># → single value dict; errors if 0 or more than one match
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This mirrors script-side &lt;code>node.R.&amp;lt;rel&amp;gt;.hasOne()&lt;/code> cardinality checks. Prefer an &lt;strong>alias&lt;/strong> when the source is named; use &lt;code>hasOne(related(...))&lt;/code> for the unaliased case instead of a bare &lt;code>[0]&lt;/code> index.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl"># Preferred when aliased
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">max(col(trial[&amp;#34;people&amp;#34;], 1))
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Unaliased edge case
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">max(col(hasOne(related(&amp;#34;value&amp;#34;, &amp;#34;input&amp;#34;, &amp;#34;spreadsheetFile&amp;#34;))[&amp;#34;people&amp;#34;], 1))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="tables-col-and-aggregates">Tables: &lt;code>col()&lt;/code> and aggregates&lt;/h2>
&lt;h3 id="colrows-index_or_name">&lt;code>col(rows, index_or_name)&lt;/code>&lt;/h3>
&lt;p>Extract one column from a &lt;strong>list of rows&lt;/strong>:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Row shape&lt;/th>
&lt;th>Second argument&lt;/th>
&lt;th>Example&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>List of lists / tuples&lt;/td>
&lt;td>Integer index (0-based)&lt;/td>
&lt;td>&lt;code>col(trial[&amp;quot;people&amp;quot;], 1)&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>List of dicts&lt;/td>
&lt;td>String key&lt;/td>
&lt;td>&lt;code>col(trial[&amp;quot;people&amp;quot;], &amp;quot;age&amp;quot;)&lt;/code>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl"># people = [[&amp;#34;fred&amp;#34;, 23], [&amp;#34;john&amp;#34;, 26], [&amp;#34;peter&amp;#34;, 92]]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">max(col(trial[&amp;#34;people&amp;#34;], 1)) # → 92
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sum(col(trial[&amp;#34;people&amp;#34;], 1))
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">avg(col(trial[&amp;#34;people&amp;#34;], 1))
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">min(col(trial[&amp;#34;people&amp;#34;], 1))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Missing keys or out-of-range indexes are &lt;strong>errors&lt;/strong> (so mistakes are visible).&lt;/p>
&lt;h3 id="list-comprehensions-escape-hatch">List comprehensions (escape hatch)&lt;/h3>
&lt;p>Still valid when you need custom logic:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">max([age for name, age in trial[&amp;#34;people&amp;#34;]])
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">max([row[1] for row in trial[&amp;#34;people&amp;#34;]])
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sum([x for x in related() if x &amp;gt; 100])
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>For plain column extract + aggregate, &lt;strong>&lt;code>col&lt;/code> is clearer&lt;/strong>.&lt;/p>
&lt;h3 id="empty-lists">Empty lists&lt;/h3>
&lt;p>&lt;code>max([])&lt;/code> and &lt;code>min([])&lt;/code> fail. Ensure the source list is non-empty, or handle emptiness upstream.&lt;/p>
&lt;hr>
&lt;h2 id="operators-and-conditions">Operators and conditions&lt;/h2>
&lt;h3 id="arithmetic-and-comparison">Arithmetic and comparison&lt;/h3>
&lt;p>&lt;code>+&lt;/code> &lt;code>-&lt;/code> &lt;code>*&lt;/code> &lt;code>/&lt;/code> &lt;code>^&lt;/code> · &lt;code>==&lt;/code> &lt;code>!=&lt;/code> &lt;code>&amp;lt;&lt;/code> &lt;code>&amp;gt;&lt;/code> &lt;code>&amp;lt;=&lt;/code> &lt;code>&amp;gt;=&lt;/code> · &lt;code>and&lt;/code> &lt;code>or&lt;/code> &lt;code>not&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">(price + tax) * qty
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">revenue &amp;gt; cost and status == &amp;#34;approved&amp;#34;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="conditional-expression-ternary">Conditional expression (ternary)&lt;/h3>
&lt;p>Use the &lt;strong>expression&lt;/strong> form, not an if-statement:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl"># Valid
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;#34;high&amp;#34; if a &amp;gt; 10 else &amp;#34;low&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">A1 if B2 &amp;gt; 0 else B2 + A1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Invalid (statement — not allowed in a single expression)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># if B2 &amp;gt; 0: A1 else: B2 + A1
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="parentheses-and-strings">Parentheses and strings&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">(price + tax) * qty
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;#34;hello&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;#39;world&amp;#39;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="built-in-functions-summary">Built-in functions (summary)&lt;/h2>
&lt;h3 id="nodlin-specific">Nodlin-specific&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Function&lt;/th>
&lt;th>Returns&lt;/th>
&lt;th>Role&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>related([prop,] [relation,] [type])&lt;/code>&lt;/td>
&lt;td>List&lt;/td>
&lt;td>Values from inbound related nodes&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>sum(list)&lt;/code>&lt;/td>
&lt;td>Number&lt;/td>
&lt;td>Sum numerics (non-numerics ignored)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>avg(list)&lt;/code>&lt;/td>
&lt;td>Number&lt;/td>
&lt;td>Average numerics&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>col(rows, index|name)&lt;/code>&lt;/td>
&lt;td>List&lt;/td>
&lt;td>Column from list-of-rows&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>hasOne(list)&lt;/code>&lt;/td>
&lt;td>Any&lt;/td>
&lt;td>Exactly one list element&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="starlark-also-available">Starlark (also available)&lt;/h3>
&lt;p>&lt;code>max&lt;/code>, &lt;code>min&lt;/code>, &lt;code>len&lt;/code>, &lt;code>abs&lt;/code>, &lt;code>sorted&lt;/code>, &lt;code>enumerate&lt;/code>, &lt;code>zip&lt;/code>, &lt;code>any&lt;/code>, &lt;code>all&lt;/code>, &lt;code>int&lt;/code>, &lt;code>float&lt;/code>, &lt;code>str&lt;/code>, &lt;code>bool&lt;/code>, &lt;code>list&lt;/code>, &lt;code>dict&lt;/code>, &lt;code>type&lt;/code>, …&lt;/p>
&lt;h3 id="modules">Modules&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">math.sqrt(16)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">math.pi
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">math.ceil(3.2)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">time.now()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># (time helpers as provided by the expression environment)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="result-types-and-display">Result types and display&lt;/h2>
&lt;p>An expression evaluates to one of:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Type&lt;/th>
&lt;th>Example&lt;/th>
&lt;th>On the node&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>Number&lt;/strong>&lt;/td>
&lt;td>&lt;code>price * qty&lt;/code>&lt;/td>
&lt;td>Formatted value (scale, currency, %)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Condition&lt;/strong>&lt;/td>
&lt;td>&lt;code>revenue &amp;gt; cost&lt;/code>&lt;/td>
&lt;td>&lt;code>TRUE&lt;/code> / &lt;code>FALSE&lt;/code> (or icons)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>List&lt;/strong>&lt;/td>
&lt;td>&lt;code>related()&lt;/code> / &lt;code>col(...)&lt;/code>&lt;/td>
&lt;td>Compact list summary&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Map&lt;/strong>&lt;/td>
&lt;td>dict result&lt;/td>
&lt;td>Key summary&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="formatting-on-the-expression-node-not-in-the-formula">Formatting (on the expression node, not in the formula)&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Setting&lt;/th>
&lt;th>Purpose&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>Scale&lt;/strong>&lt;/td>
&lt;td>Decimal places (0–8)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Currency / PCT&lt;/strong>&lt;/td>
&lt;td>e.g. USD, EUR, or percentage&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Size / colours&lt;/strong>&lt;/td>
&lt;td>Node appearance&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Show label&lt;/strong>&lt;/td>
&lt;td>Title vs comment&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>List icon options&lt;/strong>&lt;/td>
&lt;td>First / last / both for list results&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Errors surface with a clear error state on the node (and in the form) so formula mistakes are visible.&lt;/p>
&lt;hr>
&lt;h2 id="worked-examples">Worked examples&lt;/h2>
&lt;h3 id="totals-and-tax">Totals and tax&lt;/h3>
&lt;p>Aliases: &lt;code>product&lt;/code> = 29.99, &lt;code>taxRate&lt;/code> = 0.08, &lt;code>qty&lt;/code> = 3&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">(product + product * taxRate) * qty
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># → 97.1694 (format as USD, scale 2 → $97.17)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="average-cost-over-a-relationship">Average cost over a relationship&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">avg(related(&amp;#34;value&amp;#34;, &amp;#34;hasCost&amp;#34;))
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># connected costs [10, 15, 25] → 16.67
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="profit-check">Profit check&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">(product * qty) &amp;gt; sum(related(&amp;#34;value&amp;#34;, &amp;#34;hasCost&amp;#34;))
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># → True
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="spreadsheet-table-column">Spreadsheet table column&lt;/h3>
&lt;p>Spreadsheet alias &lt;code>trial&lt;/code>, &lt;code>value.people&lt;/code> = name/age rows:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">max(col(trial[&amp;#34;people&amp;#34;], 1))
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># → 92
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="single-related-spreadsheet-without-alias">Single related spreadsheet without alias&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">max(col(hasOne(related(&amp;#34;value&amp;#34;, &amp;#34;input&amp;#34;, &amp;#34;spreadsheetFile&amp;#34;))[&amp;#34;people&amp;#34;], 1))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="writing-expressions-in-the-ui">Writing expressions in the UI&lt;/h2>
&lt;p>Expression fields use a &lt;strong>CodeMirror&lt;/strong> editor (Python highlighting) with an expandable helper panel:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Named inputs&lt;/strong> — insert aliases; open &lt;strong>Fields…&lt;/strong> for dict paths&lt;/li>
&lt;li>&lt;strong>Aggregate column&lt;/strong> — for list-of-rows fields, pick column + max/min/sum/avg → inserts &lt;code>max(col(...))&lt;/code>&lt;/li>
&lt;li>&lt;strong>Related list&lt;/strong> — build &lt;code>related(...)&lt;/code> / &lt;code>sum(related(...))&lt;/code> / &lt;code>hasOne(related(...))&lt;/code>&lt;/li>
&lt;li>&lt;strong>Operators&lt;/strong> — chips and templates (&lt;code>col&lt;/code>, comprehensions, conditionals)&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>Habit that pays off:&lt;/strong> alias the source node, use &lt;strong>Fields…&lt;/strong> + &lt;strong>Aggregate column&lt;/strong>, avoid hand-written &lt;code>related()[0]&lt;/code> when &lt;code>hasOne&lt;/code> or an alias will do.&lt;/p>
&lt;p>Forms can also embed the same control via &lt;code>webform.addExpressionEditor&lt;/code> — see
.&lt;/p>
&lt;hr>
&lt;h2 id="expressions-vs-scripts-checklist">Expressions vs scripts (checklist)&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Need&lt;/th>
&lt;th>Prefer&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>One calculated number / condition / list&lt;/td>
&lt;td>&lt;strong>Expression&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Reuse that value by name on neighbours&lt;/td>
&lt;td>&lt;strong>Expression&lt;/strong> + alias&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Custom form, SVG, create/link nodes&lt;/td>
&lt;td>&lt;strong>Script&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Multi-step workflow / actions&lt;/td>
&lt;td>&lt;strong>Script&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Domain-specific node type&lt;/td>
&lt;td>&lt;strong>Script&lt;/strong> package&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="troubleshooting">Troubleshooting&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Symptom&lt;/th>
&lt;th>Likely cause&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Unknown name&lt;/td>
&lt;td>Missing alias, or node not &lt;strong>inbound&lt;/strong>-related&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>hasOne(): no items&lt;/code> / &lt;code>&amp;gt;1 items&lt;/code>&lt;/td>
&lt;td>Zero or multiple matches — fix links or filter &lt;code>relation&lt;/code>/&lt;code>type&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>col(): … out of range&lt;/code> / key not found&lt;/td>
&lt;td>Wrong column index/name or ragged rows&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>max&lt;/code>/&lt;code>min&lt;/code> on empty list&lt;/td>
&lt;td>No rows — check source path&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Invalid if/else&lt;/td>
&lt;td>Use &lt;code>a if cond else b&lt;/code>, not &lt;code>if cond: …&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Type without relation&lt;/td>
&lt;td>&lt;code>related&lt;/code> requires &lt;code>relation&lt;/code> whenever &lt;code>type&lt;/code> is set&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="further-reading">Further reading&lt;/h2>
&lt;ul>
&lt;li>Implementation-oriented language notes live with the expression agent in the server repo (&lt;code>NODLIN_EXPRESSION_LANGUAGE_SPEC.md&lt;/code>).&lt;/li>
&lt;li>Script API (including relationship &lt;code>hasOne&lt;/code> on nodes):
&lt;/li>
&lt;li>
&lt;/li>
&lt;/ul></description></item></channel></rss>