Field Mapping
How to reference values from earlier steps using {{template}} expressions.
Field Mapping
Almost every node needs values from somewhere else — the trigger payload, the output of a previous action, a nested field three levels deep in a JSON response. Field mapping is how you wire those values into a node's inputs.
Triggo uses a small template language that looks like {{source.path.to.value}}. You write these expressions anywhere the inspector shows a text field; the executor resolves them at run time against the live outputs of upstream nodes.
Syntax
The delimiter is double curly braces: {{... }}. Everything inside is a dot-separated path. The implementation lives in.
There are two resolution modes, picked automatically:
- Whole-value — the entire input field is one expression, like
{{trigger.email}}. The resolved value is returned as-is, preserving its type. A number stays a number, an array stays an array, an object stays an object. - Inline — the expression is embedded in a larger string, like
Hello {{trigger.name}}!. Each expression is resolved and stringified into the surrounding text.nullandundefinedbecome the empty string.
Addressing the trigger
The trigger is special — it gets a reserved root key:
{{trigger.field}}Under the hood, the executor stores each node's output under its node ID. When you write {{trigger...}}, the resolver first looks for a literal trigger key, and if that's absent, falls back to the first key that starts with trigger (for example trigger_1). This means you can write {{trigger.email}} regardless of how the canvas has named the trigger node internally.
Addressing a prior step
Any other node is addressed by its node ID — the same ID the canvas assigns when you drop the node in:
{{action_1.id}}
{{http_request_2.body.status}}Open a node in the inspector to see its ID. The field mapping picker in the UI inserts these for you, so you rarely type them by hand.
Addressing nested values
Paths are plain dot notation. Each segment after the root is treated as an object key:
{{trigger.contact.email}}
{{action_1.customer.address.city}}Traversal is null-safe in the sense that it returns undefined the moment any segment is missing or non-object — it does not throw. See traversePath in field-mapper.
A few things that do not exist today:
- Array index syntax — there is no
{{items.0.name}}or{{items[0].name}}. The resolver splits on.and treats every segment as an object key, so numeric segments only work when the underlying value is an object with that numeric key as a string. To pick an element out of an array, use a Code node. - Optional chaining (
?.), null coalescing (??), or default values — not supported. A missing path resolves toundefined, period. - Function calls, filters, or expressions — the template language has no operators. If you need
toUpperCase(),slice(), arithmetic, or conditional logic, use a Code node.
Type coercion
When an expression feeds a connector action, the executor coerces the resolved value to the property's declared type. Rules live in — one strategy per property type:
NUMBER— if already a number, pass through. OtherwiseNumber(value); throwsVALIDATION_ERRORif the result isNaN. So{{trigger.age}}returning the string"25"becomes25.CHECKBOX— booleans pass through. Strings"true"/"1"(case-insensitive) →true;"false"/"0"→false. Anything else throwsVALIDATION_ERROR.SHORT_TEXT— alwaysString(value). Numbers, booleans, even objects become their string form.LONG_TEXT— same asSHORT_TEXT:String(value).ARRAY— arrays pass through. A string is parsed as JSON and accepted if it parses to an array; anything else throws. Non-string, non-array values pass through unchanged (the connector sees whatever they are).OBJECT— objects pass through (includingnull, sincetypeof null === "object"). Strings are JSON-parsed; the result must be a non-null object or it throws. Non-string, non-object values pass through unchanged.DROPDOWN,DYNAMIC_DROPDOWN,OAUTH2,SECRET_TEXT,DATE_TIME— identity. The value is forwarded untouched; the connector is responsible for parsing.
Null safety across the board: if the resolved value is null or undefined, coercion is skipped and the raw null/undefined is passed through. A missing field never throws at coercion time — it will usually fail later when the connector validates required inputs.
Escaping
There is no escape mechanism for a literal {{ or }}. The regex is \{\{([^}]+)\}\} — any double-brace pair with non-} content in between is treated as a template. Emitting a literal {{foo}} in output text is not supported; if you genuinely need to, build the string in a Code node and return it from there.
In practice this almost never comes up — workflow inputs are values, not templating source code.
Missing paths
This is intentional behaviour worth knowing:
- Unknown root key →
undefined. If you write{{tyop.field}}and no node with IDtyophas run, the expression resolves toundefined. The resolver does not search the rest of the input data for a matching key. This is deliberate — it surfaces typos and broken references instead of silently picking up some unrelated value. - Unknown nested segment →
undefined. Walking a missing key or traversing throughnullyieldsundefined. - Inline templates stringify
undefinedto"". SoHello {{trigger.unknown}}!renders asHello !. - Whole-value templates preserve
undefined. A field mapping whose entire value is{{trigger.unknown}}passesundefinedthrough, and (per the coercion rules above) coercion skips it. Required-field validation at the connector boundary is where this usually surfaces.
There is no global fallback and no auto-search. If you address a node that isn't upstream of the current node, you get undefined.
Common pitfalls
- Bare field names don't work.
{{email}}looks natural but resolves toundefinedunless a node with IDemailexists. You almost always mean{{trigger.email}}or{{some_action.email}}. The root segment is a node ID, not a field name. - Typos silently return
undefined.{{trigger_1.emial}}(note the transposed letters) returnsundefined, which stringifies to""in inline contexts. If a downstream node is sending a blank string where a value should be, start by re-checking the spelling of the path. - Numbers come through as strings from some sources. Webhook bodies, URL params, and form fields often arrive as strings.
NUMBER-typed properties coerce them for you;SHORT_TEXTdoes not (there's nothing to do), but if you write a condition against{{trigger.body.count}}expecting integer comparison, remember the raw value may be"25"rather than25. Condition operators handle this, but custom Code logic must too. - No array indexing.
{{list_rows_1.rows.0.name}}does not pluck the first row — it looks up a key literally called"0"onrows. Arrays of results need a Loop node or a Code node to pick items. - Referencing a sibling branch. A node can only see outputs from its ancestors in the DAG. Pointing
{{other_branch.result}}at a node in a parallel branch that hasn't run yet will resolve toundefined. If you need cross-branch data, merge the branches first.
Related
- Passing data between nodes
- Workflow Builder Overview
- Code node — for anything the template language can't express