Templates

A template is text with {{ ... }} substitutions that a node evaluates when it runs. This is how a prompt, an HTTP body or a message picks up values from earlier nodes, variables and secrets without a line of code.

Where templates work, and where they don’t

It is the field that is templatable, not the node. The editor renders such a field as a code editor with name completion and highlighting for unknown names — for example template on template or system_prompt on ai_agent.

The engine hands a node its configuration verbatim; the substitution is done by the node itself, and only for fields declared templatable. A field without that marker takes text literally: {{ ... }} stays two curly braces.

app.iterna.ai

Node configuration

Template

Parameters

Template
The customer wrote: {{ inputs.input }}

Substitutes the value delivered to the Input port.

A templatable field in the node parameters.

Namespaces

Name What it holds
inputs.<handle> The value the immediate predecessor delivered on that port. The name is relative to each node: inputs.input on a node in the middle of the graph is its predecessor’s output, not the workflow input.
nodes.<id>.output Another node’s primary value — exactly what its success edge carries downstream. Remaining result keys sit beside it: nodes.<id>.status, nodes.<id>.branch.
variables.<name> Session variables declared by a var node. Inside while_loop the loop_index counter joins them.
secret.<NAME> Workspace secrets (Settings, “Secrets”). Decrypted once per run and available in any node.
loop Fields of the current iteration inside an iteration shell, e.g. loop.item.
telegram.reply_message_id Id of the bot’s reply message — Telegram turns only, empty until the reply is sent.

There is a shorthand as well: {{ my_node.output }} behaves like {{ nodes.my_node.output }}, and an input port name is available directly. When a node id is not a valid Jinja identifier, write {{ nodes['my-node'].output }}.

Entry
Template
LLM Response
LLM
Exit
  • Execute + Data
  • LLM
inputs comes from the neighbour on the left; nodes.<id> from any node by its id.

A value or a string

One rule, and it matters for fields that need a list or a number:

  • the whole string is a single expression — {{ inputs.input.items }} — and the native value comes back (a list, a number, a dict);
  • the expression is mixed with text, or the string contains {% ... %} blocks, and the result is always a string.

Time and missing values

You get {{ now() }} (UTC; now('Europe/Moscow') for a zone) and the | datetimeformat('%d.%m.%Y %H:%M') filter, aliased as | strftime. It accepts a datetime, a unix number or an ISO string; anything it cannot read renders as an empty string instead of failing the node. There are no modules and no import in templates — for arbitrary logic use code_javascript.

An unknown name does not fail the node either: it renders empty. The empty paths a render touched are recorded on the node execution under __undefined_refs__ — that is the only trace a typo in an id leaves, so check it whenever a field comes out unexpectedly blank.

One pass, and it is a security property

Every templatable field is rendered exactly once. A value that has already been through a render is never rendered again, even if the substitution introduced {{ }} into it.

That is deliberate: after the first pass the string legitimately carries user data, a database row or a model answer. A second pass would execute that text as a template in a context that contains secret.*.

Checking the result

Open a run and expand the node: its debug panel has a section with the rendered templates, so you can see what actually went to the model or into the HTTP request, together with __undefined_refs__ when something did not resolve. In the editor, unknown names are highlighted in the field itself, before any run.