Skip to main content

Variables

Every string a Flow node sends, requests or evaluates can contain {{...}} tokens. At run time the engine builds a template context for the step and replaces each token with the value at that path.

Hi {{contact.first_name}}, your order {{vars.api_result.body.order.id}} ships {{vars.eta}}.

Namespaces

NamespaceWhat it holdsExamples
contactThe contact the run belongs tocontact.first_name, contact.email, contact.phone, contact.language, contact.custom.loyalty_tier, contact.custom["plan-tier"], contact.tags, contact.lists, contact.methods[0].value
messageThe contact's most recent inbound messagemessage.text, message.channel_type, message.created_at, message.attachments[0].url
conversationThe conversation the run is attached to (if any)conversation.id, conversation.status, conversation.channel_type, conversation.messages[-1].text
triggerThe trigger_payload the run was started withtrigger.amount_paid, trigger.order.items[0].sku
varsFlow variables written by earlier nodes (Collect Input, KB query, Create Tracked Link, API Call, Custom Code)vars.captured_email, vars.api_result.status, vars.order_total
flowRun metadataflow.id, flow.run_id, flow.name, flow.entry_at, flow.last_channel_used, flow.stop_url
secretsStored flow secrets — API Call node onlysecrets.STRIPE_KEY

{{flow.stop_url}} renders a signed opt-out link (see Examples). Legacy aliases such as {{contact.custom_fields.x}} and {{vars.stop_flow_url}} keep working.

Path grammar

identifier ( "." identifier | "[" integer "]" | "[" quoted-string "]" )*
  • Dots walk into objects: vars.order.customer.email.
  • Integer indices walk into arrays and may be negative: vars.items[0], vars.items[-1].
  • Quoted keys handle names with dashes or dots: contact.custom["plan-tier"].
  • A numeric segment against an object tries the string key (vars.map[0] reads "0").
  • Whitespace inside the braces is ignored: {{ vars.x }}.

A path that does not exist renders as an empty string — never an error. Use a Condition node (or {{vars.api_result.ok}}) to guard against missing data.

How values become text

ValueRenders as
stringas-is
number42, 3.5
booleantrue / false
null / missingempty string
object / arraycompact JSON ({"a":1})

Typed rendering in JSON bodies

The API Call node's body_json is a JSON template. A token placed outside quotes keeps the native type of the value it points to; a token inside quotes is interpolated as text.

{
"order_id": {{vars.api_result.body.order.id}},
"items": {{vars.api_result.body.order.items}},
"customer": "{{contact.first_name}} {{contact.last_name}}",
"paid": {{trigger.paid}}
}

renders (for a paid order 42 with two items) as

{"order_id": 42, "items": [{"sku": "X1", "qty": 2}, {"sku": "Y2", "qty": 1}], "customer": "Ada Lovelace", "paid": true}

The template must be valid JSON once every bare token is treated as a string placeholder — the validator rejects a body it cannot parse at save time, so a stray comma is caught in the editor, not at run time.

Secrets

Store credentials once under Flows → Secrets (or PUT /api/v1/flows/secrets/{NAME}) and reference them as {{secrets.NAME}} inside an API Call node's URL, headers, auth block or body. Names are upper-case identifiers (^[A-Z][A-Z0-9_]{0,63}$, lower-case input is upper-cased for you).

  • Values are write-only: list and detail responses carry only a masked preview (sk_…GH).
  • Publishing fails if a flow references a secret that does not exist; saving shows it as a warning.
  • Secrets are resolved right before the request and are never exposed to Custom Code, Send nodes or webhooks.
  • Anywhere a resolved value would appear in the step audit (request headers, URL, response body) it is replaced with ***. Standard credential headers (Authorization, Cookie, X-API-Key, ...) are masked regardless of their value.

Where variables come from

NodeWrites
Collect Inputvars.<output_var_name> — the validated answer
KB queryvars.<output_var> — retrieved passages
Create Tracked Linkvars.<output_var_name> — the short URL
API Callvars.<output_var>{ok, status, headers, body, duration_ms, error}
Custom Codeone vars.<key> per key of the returned object, plus vars.<error_var> on failure

Every one of these paths can also be tested directly in a Condition or Branch rule — as the rule's field (vars.api_result.body.count, with or without the {{ }} wrapper) or inside its value ("{{vars.threshold}}") — with an optional value_type cast (number, boolean, date, string). See Condition (Branch). secrets.* is the one namespace Conditions never resolve.

GET /api/v1/flows/variables?flow_id=... returns the same catalogue the editor's variable picker uses — see Triggers and the API.

Test context

The editor's Test request / Run script buttons and Flow test runs build the same context as a real run. Without a contact_id they substitute a sample contact ("Sam Sample") and a sample inbound message so {{contact.*}} and {{message.*}} tokens still render; vars.is_test is true in both cases.