Triggo Documentation
Workflow Builder

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. null and undefined become 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 to undefined, 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. Otherwise Number(value); throws VALIDATION_ERROR if the result is NaN. So {{trigger.age}} returning the string "25" becomes 25.
  • CHECKBOX — booleans pass through. Strings "true" / "1" (case-insensitive) → true; "false" / "0"false. Anything else throws VALIDATION_ERROR.
  • SHORT_TEXT — always String(value). Numbers, booleans, even objects become their string form.
  • LONG_TEXT — same as SHORT_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 (including null, since typeof 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 ID tyop has run, the expression resolves to undefined. 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 through null yields undefined.
  • Inline templates stringify undefined to "". So Hello {{trigger.unknown}}! renders as Hello !.
  • Whole-value templates preserve undefined. A field mapping whose entire value is {{trigger.unknown}} passes undefined through, 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

  1. Bare field names don't work. {{email}} looks natural but resolves to undefined unless a node with ID email exists. You almost always mean {{trigger.email}} or {{some_action.email}}. The root segment is a node ID, not a field name.
  2. Typos silently return undefined. {{trigger_1.emial}} (note the transposed letters) returns undefined, 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.
  3. 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_TEXT does 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 than 25. Condition operators handle this, but custom Code logic must too.
  4. No array indexing. {{list_rows_1.rows.0.name}} does not pluck the first row — it looks up a key literally called "0" on rows. Arrays of results need a Loop node or a Code node to pick items.
  5. 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 to undefined. If you need cross-branch data, merge the branches first.

On this page