Skip to content

Wires and named tables

A wire on the canvas carries one port’s rows into one input port on the node you drew it to. That input port’s id is the wire’s handle, and the handle is also the name an operation reads the wire by. A node with three wires has three handles, and nothing about those three rows sets is combined until an operation on the node says how.

The first wire into a node lands on its default input port, input. Each further wire mints its own input port, named after the source node’s title (prices, prices-2 when that name is taken). Select the wire and open the Overview panel to read its handle and rename it; the rename moves the input port with it, so the label on the canvas and the name your formula uses stay the same word.

One handle carries one wire. Drawing a second wire onto a node gives it a new handle rather than stacking two sources on one port, so every table a node reads has exactly one source.

Wires into the node The first operation’s rows Named tables
None, as on a Load file, a Static Data table or a trigger Empty None
One That wire’s rows, with their schema, audit, lineage and styles That wire, on its handle
Several Empty Every wire, on its own handle

With one wire, the common case, the node reads it and you write nothing. With several, the node starts empty and the merge formula builds the row set, which is why a new node carries a merge slot in front of its native operation. See Operation node.

inputs() is the node’s set of named tables, and inputs().prop(handle) is one of them:

setProp("venue_name", inputs().prop("venues").find("code", prop("venue")).prop("name"))

find returns the first matching row, findAll every matching row, and rows() all of them. A value you read out of a table row brings that cell’s data styles with it, so an enriched column keeps the marks of the column it came from.

A Schema node names the same tables in a property’s source path, as <handle>.<field>. A Jinja template and an AI prompt read them as {{ inputs }}. A script reads them as ctx["tables"].

These five shapes cover what per-edge merge settings used to do. Each one is a formula operation in the node’s merge slot.

let(name, value, body) is a three-argument expression, so a let binding never survives to the next statement: each column reads the table in its own statement, and a statement that needs the matched row twice holds the let inside itself.

Append both wires

appendRows(inputs().prop("trades_eu"))
appendRows(inputs().prop("trades_us"))

Enrich one wire from another by key

appendRows(inputs().prop("trades"))
setProp("price", inputs().prop("prices").find("ISIN", prop("ISIN")).prop("price"))
setProp("priceDate", inputs().prop("prices").find("ISIN", prop("ISIN")).prop("asOf"))

Enrich without overwriting a value the row already has

appendRows(inputs().prop("trades"))
if(
empty(prop("currency")),
setProp("currency", inputs().prop("reference").find("ISIN", prop("ISIN")).prop("currency"))
)

Full merge by key: keep the rows only one side has

appendRows(inputs().prop("left"))
appendRows(inputs().prop("right"))
groupBy(prop("id"))
setProp("id", prop("id").first())
setProp("amount", prop("amount").filter(not(empty(current))).first())

Both sides stack, then the key collapses them. A column only one side carries reads empty on the other side’s rows, so the aggregate skips empties and keeps the first value that is there, which is the left side where both carry one. A key only one side has still gets its row. appendRows takes a table, never a filtered row list, so the one-sided rows are kept this way rather than appended on their own.

Match by position

appendRows(inputs().prop("a"))
setProp("amount", inputs().prop("b").rows().at(rowIndex()).prop("amount"))

rowIndex() is the current row’s zero-based position in the row set at that statement, and at() past the end is empty, so a shorter second table leaves those cells empty rather than failing the node.

Statements are ordered, so where two of them write the same field the later one wins, and the style on that cell belongs to the write that landed last. That order is the whole rule: a wire has no priority of its own.

An operation that reads a wire whose upstream port carries a warning or error exception fails closed: the node writes no rows and does not run its remaining operations. Reading a clean wire beside it does not rescue the node. Clear the upstream exceptions, or branch the graph so the clean path is separate. See Exception management.