<!-- Generated from the canonical Teloring documentation source. Do not edit this file. -->

Canonical page: https://docs.teloring.com/docs/product/studio/variables
Last modified: 2026-08-20T20:49:57.000Z

# 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:

```text
Hi {{contact.name}}, we received your message about {{message.content}}.
```

| Rule | Detail |
| --- | --- |
| Format | `{{namespace.key}}` — for example `{{conversation.id}}` |
| Case | Keys are lowercase with dots. |
| Unknown keys | Left exactly as they are, so a typo shows up in the output instead of silently vanishing. |
| Where they work | Any 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](pathname:///img/screenshots/product/studio/variables-panel.png)

| Action | Result |
| --- | --- |
| Drag a variable into a field | It is inserted at the drop point. |
| Click a variable | It is copied, ready to paste. |
| Click a namespace heading | Expands 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

| Namespace | Comes from | Example |
| --- | --- | --- |
| `system.*` | Always available | `{{system.timestamp}}` |
| `var.*` | [Save as Variable](./actions.md#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-attributes.md) | `{{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.

| Variable | Holds |
| --- | --- |
| `{{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.

| Property | Behavior |
| --- | --- |
| Created by | The [Save as Variable](./actions.md#save-as-variable) action |
| Scope | Per flow — two flows can each hold their own `var.counter` |
| Availability | Loaded into **every** run of that flow, so they work from the first block onward |
| Namespace | Forced into `var.`, so a saved value can never overwrite a system, contact, conversation, or message variable |
| Dynamic keys | Allowed — `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](./triggers.md) page; here is the short version.

| Trigger | You get |
| --- | --- |
| Incoming Message | `message.*`, `conversation.*`, `contact.*`, `channel.*` |
| Incoming Call | `call.*`, `conversation.*`, `contact.*`, `channel.*` |
| Form filled | `form.*`, `submission.id`, `answers.*`, `hiddenValues.*`, `sourceParams.*`, `contact.id`, `conversation.id` |
| Conversation Changed | `conversation.*` (including `previous_*`), `change.*`, `ai.*`, `contact.*`, `channel.*` |
| Customer trigger | `customer.*` (including `customer.old` / `customer.new`), `event.*` |
| Customer record trigger | `record.*`, `objects.*`, `customer.*`, `event.*` |
| Agent status changes | `agent.*` |
| Recurring Schedule / Scheduled Time | `schedule.*` |
| Incoming webhook | `webhook.*`, plus one variable per learned field |
| Analytics Alert | `alert.*` |

### Object and array variables

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

| Variable | Shape | Reach 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 answers | Pass it whole to an HTTP request, or loop over it in a [Code](./actions.md#code) block |
| `{{customer.old}}` · `{{customer.new}}` · `{{record.old}}` · `{{record.new}}` | Before/after snapshots | `{{record.new.status}}` |
| `{{conversation.custom_attributes}}` | Every [conversation attribute](../conversation-attributes.md) | `{{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](../conversation-attributes.md) 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
```

| Fact | Detail |
| --- | --- |
| Where they come from | The **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 written | A [Change Conversation](./actions.md#setting-conversation-attributes) block that sets attributes updates these variables, so every block below it reads the **new** values. |
| Never filled in | Resolves to an empty value rather than leaving the raw `{{placeholder}}` in your request. |
| Naming | The **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:

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

## Variables in a JSON body

The [HTTP Request](./actions.md#http-request) block is type-aware when it fills a JSON body:

```json
{
  "customer": "{{contact.name}}",
  "answers": {{answers.list}},
  "greeting": "Hi {{contact.name}}!"
}
```

| Placement | Result |
| --- | --- |
| A variable filling a whole value, bare or fully quoted | Keeps its real JSON type — an array stays an array, a number stays a number |
| A variable inside a longer string | Substituted as text, with quotes and special characters safely escaped |
| A variable that resolves to nothing | Left untouched |

## Variables in a Code block

Inside a [Code](./actions.md#code) block you can either substitute a variable into the source, or read it from the frozen `variables` object:

```javascript
// 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](./conditions.md#condition-ifelse) rule, the **Value / variable** field takes a plain key **without** the braces:

| Field | Write |
| --- | --- |
| Value / variable | `message.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](./actions.md#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

- [Actions](./actions.md) — every block's outputs.
- [Triggers](./triggers.md) — every trigger's variables.
- [Troubleshooting](./troubleshooting.md) — what to do when a variable renders empty.
