Skip to main content
Ingestly templates pull data from earlier nodes into the configuration of later ones. Wherever you see a field that accepts {{ ... }} (callback URL, HTTP body, email subject, transform output, validation rule), you can reference an upstream value, index into arrays, transform it with filters, or insert a built-in helper.

Basic syntax

A template expression has three parts: a path, optional filters, and the surrounding {{ }} markers.
The path locates the value. Filters transform it left to right. The result replaces the whole {{ ... }} token.

Path

A path is a dotted identifier, optionally with array indices. The first segment of every path must be the name of an upstream node (or a built-in placeholder or helper, see below). Subsequent segments index into that node’s output, which has the shape { success, payload, metadata, error }. Paths are case sensitive and may use letters, digits, and underscores.
Each node in the editor has a Name field. Default names are auto-assigned (extract, extract2, vars, etc.) but you can rename a node to anything that’s unique within the pipeline. Whatever name you pick is the identifier used at the head of every template expression.

Built-in placeholders

These resolve from the run context:

Source document file

The $document head resolves from the run’s source document: the original file that triggered the run, whether it was uploaded or emailed. It is a global head, valid in any template field, because a pipeline can have several triggers but only one fires per run. The metadata paths (name, contentType, size, extension) are always cheap to resolve. base64 is different: the file bytes are fetched and encoded lazily, only when a template actually references {{$document.file.base64}}, and they are never persisted. The encoded value never enters run output, step history, or the live preview. Anywhere it would otherwise appear, Ingestly shows a [binary N bytes, type] placeholder instead. Use {{$document.file.base64}} to send the source file as a binary body on a callback or HTTP node, or to attach it to a record.
A node name can never start with $, so the $document head never collides with a node you have named document.

Built-in helpers

These produce a value at the time the template is evaluated. You can chain filters onto helpers: {{now | formatDate: 'yyyy-MM-dd'}}.

Filters

Add a filter with the pipe character: {{ value | filter }}. Filters with arguments use a colon: {{ value | filter: 'arg1', 'arg2' }}. You can chain as many filters as you need, applied left to right.

Filter arguments

Each argument is one of: Path references must satisfy the same shape as the head path (identifier(.identifier|[N])*). The runtime resolves them at filter-application time, so they can reference upstream node fields (extract.payload.divisor), built-in helpers ({{end | dateDiff: 'days', start}}), or array elements ({{items[0] | concat: items[1]}}). If the path resolves to a type that doesn’t match what the filter expects (e.g., div argument resolves to a string), the editor flags it before the run starts.

String filters

truncate counts the postfix toward the limit; padStart and padEnd pad with a space when no pad string is given.

Number filters

Format filters

Turn a raw number into a display string. formatCurrency takes an ISO 4217 currency code (for example USD, EUR, GBP) and pins the currency symbol from that code.

Array filters

slice uses Python-style bounds (negative indices count from the end). groupBy groups an array of objects by the string value at key, returning an array of { key, items } groups. reverse also reverses a string’s characters.

Date filters

Operate on ISO 8601 strings (or anything that can be parsed as one). The add/subtract and start/end filters return an ISO 8601 UTC string. formatDate uses .NET-style format strings. Common patterns: yyyy-MM-dd, yyyy-MM-ddTHH:mm:ssZ, MM/dd/yyyy, HH:mm. Negative arguments to the add* filters subtract.

Encoding filters

General filters

Array constructs

Beyond the filters above, four constructs transform arrays and pick branches. They use the same | syntax but are written by name.

Live preview

When you edit a template field in a node editor, Ingestly shows a live preview of the resolved value. Select a document from the run toolbar so the preview evaluates against that document’s most recent run data. This lets you confirm a path and its filter chain produce the value you expect before you run the pipeline. Without a selected document, the editor still validates the path and filter names but cannot show a resolved value.

Type-aware autocomplete

When you type inside a {{ ... }} field in a node editor, Ingestly suggests:
  • Upstream node fields at the start of the expression (after {{)
  • Built-in helpers (now, today, uuid, nowUnix) alongside fields
  • Filters after a |, scoped to the type of the preceding value (an array path shows length, first, join; a string path shows upper, lower, trim)
  • Filter arguments as snippet placeholders you can tab through
Unknown filter names render with a red squiggle and surface as a validation error on the node.

Inside JSON bodies

When you write a template inside a JSON value position, like "qty": "{{payload.qty}}", Ingestly emits the resolved value as raw JSON so types are preserved. A number stays unquoted, an object stays structured, an array stays an array.
After substitution the request body becomes:
This applies to the HTTP action and callback output body fields when they parse as JSON. Outside JSON value positions (URLs, email subjects, plain-text fields), templates substitute as plain strings.
Filter arguments inside a JSON-position template should use single quotes ('a') rather than double quotes, to avoid clashing with the surrounding JSON string.

Row expansion in JSON bodies (eachandeach and when)

A JSON body can fan out over a runtime array. Inside any JSON array, an object that carries a $each key is a row template: it is repeated once per element of the referenced array, and the $each key itself is removed from the output.
Inside a row template:
  • {{item}} is the current array element, and {{item.field}} reads a field from it
  • {{index}} is the zero-based position
  • Values that are exactly one {{item...}} or {{index}} token keep their type (a number stays a number, an object stays structured)
  • Tokens that mix text and bindings, like "row {{index}}: {{item.mark}}", substitute as text
  • Other {{node.path}} tokens are left for normal substitution
Add a $when key to filter rows. The condition supports ==, !=, a bare truthiness test, and ! for negation:
Several row templates in one array concatenate in order, which routes rows to different shapes: here regular lines (no __label) become item lines and reconcile-classified surcharge rows become a different line shape. A reconcile node stamps classified extra rows with __label, so this pairs naturally with Expected extras. An object with only $when (no $each) is included or dropped as a whole. Row templates nest: a field inside a row can hold another array with its own $each over {{item.subArray}}, and the inner template’s item refers to the inner element. Row expansion applies to the HTTP action and callback body fields when they parse as JSON. A $each reference that does not resolve to an array contributes no rows.

Common recipes

Last item from an extracted line items array, with a fallback:
Total of a numeric array, rounded:
Today’s date plus 30 days as a yyyy-MM-dd string:
Stringified JSON of a structured value, for a plain-text field:
Comma-joined tags, uppercased:

When a template doesn’t resolve

If the path can’t be resolved (the field is missing, the upstream node didn’t run, the index is out of bounds), Ingestly substitutes an empty string. Use default: '...' to provide a fallback. If the filter name is unknown or a filter call is malformed (wrong arg count, bad argument type), the substitution yields an empty string and the node surfaces a validation error.