Skip to main content

Variables

A variable is a placeholder that Studio fills in while the flow runs. Write {{contact.name}} in a message and the customer sees Dana.

Variables are what make a flow feel personal instead of canned, and they are how one block passes information to the next.

The syntax

Always two curly braces around a key:

Hi {{contact.name}}, we received your message about {{message.content}}.
RuleDetail
Format{{namespace.key}} — for example {{conversation.id}}
CaseKeys are lowercase with dots.
Unknown keysLeft exactly as they are, so a typo shows up in the output instead of silently vanishing.
Where they workAny text field, text area, URL, header value, JSON body, email field, or condition operand.

Inserting a variable

Select a block and open Available Variables in the right panel. It lists everything the blocks above this one produce, grouped by namespace.

Available Variables panel

ActionResult
Drag a variable into a fieldIt is inserted at the drop point.
Click a variableIt is copied, ready to paste.
Click a namespace headingExpands or collapses that group.
tip

The list is position-aware. A block near the top of the flow sees fewer variables than a block at the bottom, because it genuinely has less to work with. If a variable you expect is missing, the block that produces it is not upstream of the block you selected.

The namespaces

NamespaceComes fromExample
system.*Always available{{system.timestamp}}
var.*Save as Variable — persists between runs{{var.last_order_id}}
message.*Incoming Message trigger{{message.content}}
conversation.*Most conversation triggers and actions{{conversation.id}}
conversation.custom_attributes.*Your account's conversation attributes{{conversation.custom_attributes.outcome}}
contact.*Conversation and message triggers{{contact.name}}
channel.*Conversation and message triggers{{channel.name}}
customer.*Customer triggers{{customer.name}}
record.* · objects.*Customer record trigger and Customer Record action{{record.id}}
agent.*Agent status trigger{{agent.name}}
call.* · voice.*Voice trigger and voice actions{{call.caller_id}}
form.* · answers.* · hiddenValues.* · sourceParams.*Form filled trigger{{answers.email}}
webhook.*Incoming webhook trigger{{webhook.body.order_id}}
schedule.*Schedule triggers{{schedule.triggered_at}}
alert.*Analytics Alert trigger{{alert.current_value}}
change.* · ai.*Conversation Changed trigger{{change.type}} · {{ai.signal}}
reply.* · client_reply.*Reply Message action{{client_reply.content}}
http.*HTTP Request action{{http.json.status}}
email.*Send Email action{{email.status}}
code.*Code action{{code.result}}
datetime.*Date and Time action{{datetime.result}}
condition.* · availability.* · business_hours.*Condition blocks{{availability.status}}
session.* · saved_variable.* · private_note.* · wait.*The matching actions{{session.resolved}}

System variables

Always present, in every flow, from the very first block.

VariableHolds
{{system.timestamp}}The current time, in ISO format
{{system.account_id}}Your account ID
{{system.flow_id}}The flow that is running
{{system.execution_id}}This specific run

{{system.execution_id}} is worth including in HTTP requests and email headers — it is the fastest way to tie an external record back to one Studio run.

Saved variables — var.*

Ordinary variables live for exactly one run. A saved variable lives forever.

PropertyBehavior
Created byThe Save as Variable action
ScopePer flow — two flows can each hold their own var.counter
AvailabilityLoaded into every run of that flow, so they work from the first block onward
NamespaceForced into var., so a saved value can never overwrite a system, contact, conversation, or message variable
Dynamic keysAllowed — var.{{contact.phone}}.address stores one value per phone number
Managed in⚙ Flow Settings → Saved variables, where you can review and delete them

Reference by trigger

Which variables you have depends on what started the flow. Each trigger's full list is on the Triggers page; here is the short version.

TriggerYou get
Incoming Messagemessage.*, conversation.*, contact.*, channel.*
Incoming Callcall.*, conversation.*, contact.*, channel.*
Form filledform.*, submission.id, answers.*, hiddenValues.*, sourceParams.*, contact.id, conversation.id
Conversation Changedconversation.* (including previous_*), change.*, ai.*, contact.*, channel.*
Customer triggercustomer.* (including customer.old / customer.new), event.*
Customer record triggerrecord.*, objects.*, customer.*, event.*
Agent status changesagent.*
Recurring Schedule / Scheduled Timeschedule.*
Incoming webhookwebhook.*, plus one variable per learned field
Analytics Alertalert.*

Object and array variables

Some variables hold a whole object or list rather than a single value:

VariableShapeReach inside it with
{{http.json}}The response object{{http.json.order_id}}
{{webhook.body}}The request body{{webhook.body.customer.email}}
{{webhook.query}} · {{webhook.headers}}Objects{{webhook.query.token}}
{{answers}}Answers keyed by field ID{{answers.email}}
{{hiddenValues}}Hidden values keyed by field ID{{hiddenValues.campaign_id}}
{{sourceParams}}URL parameters{{sourceParams.utm_source}}
{{answers.list}}An array of answersPass it whole to an HTTP request, or loop over it in a Code block
{{customer.old}} · {{customer.new}} · {{record.old}} · {{record.new}}Before/after snapshots{{record.new.status}}
{{conversation.custom_attributes}}Every conversation attribute{{conversation.custom_attributes.order_number}}

The exact keys inside these objects come from your data. {{answers.email}} only works if a form field has the Field ID email; {{webhook.body.order_id}} only works if the payload actually contains order_id. For webhooks, use Learn next request and Studio will list the real keys for you.

Conversation attributes

Conversation Attributes are your account's own fields on a conversation — Reason for contact, Outcome, Order number. Each one becomes a variable named after its API ID:

{{conversation.custom_attributes.reason_for_contact}}
{{conversation.custom_attributes.order_number}}
{{conversation.custom_attributes}} ← the whole set, as one object
FactDetail
Where they come fromThe Incoming Message, Incoming Call and Conversation Changed triggers all load the conversation's current values, so they are available from the first block.
After they are writtenA Change Conversation block that sets attributes updates these variables, so every block below it reads the new values.
Never filled inResolves to an empty value rather than leaving the raw {{placeholder}} in your request.
NamingThe API ID, not the label. Renaming an attribute's label is safe; renaming its API ID changes the variable name.

Send the whole set to an external system in one field by putting {{conversation.custom_attributes}} in a JSON body, or pick one out for a lookup:

{
"conversation_id": "{{conversation.id}}",
"order": "{{conversation.custom_attributes.order_number}}",
"disposition": {{conversation.custom_attributes}}
}

Variables in a JSON body

The HTTP Request block is type-aware when it fills a JSON body:

{
"customer": "{{contact.name}}",
"answers": {{answers.list}},
"greeting": "Hi {{contact.name}}!"
}
PlacementResult
A variable filling a whole value, bare or fully quotedKeeps its real JSON type — an array stays an array, a number stays a number
A variable inside a longer stringSubstituted as text, with quotes and special characters safely escaped
A variable that resolves to nothingLeft untouched

Variables in a Code block

Inside a Code block you can either substitute a variable into the source, or read it from the frozen variables object:

// substituted before the code runs
const name = "{{contact.name}}";

// read by key — better for values that might contain quotes
const phone = variables["contact.phone"];

return name + " / " + phone.replace(/^\+/, '');

Variables in a condition

In a Condition If/Else rule, the Value / variable field takes a plain key without the braces:

FieldWrite
Value / variablemessage.content
Compare to (as Variable)var.expected_answer
Compare to (as Text)sales

Practical tips

  • Personalize safely. {{contact.name}} can be empty for a brand-new WhatsApp contact. Either accept a slightly bare greeting, or branch on contact.name with the exists operator first.
  • Copy IDs into your systems. {{conversation.id}}, {{contact.id}}, and {{system.execution_id}} are the values worth pushing into an external CRM so both sides can be reconciled later.
  • Use the panel, not memory. Dragging a variable from Available Variables guarantees the key is spelled the way the runtime expects.
  • Print it to check it. If you are not sure what a variable holds, drop a temporary Private Note into the branch containing it. The note is internal, so it costs you nothing and shows the resolved value exactly as the flow saw it.

Next