> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dmly.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Variables reference

> Every token you can put in a message: where each one gets its data, and when it comes out empty.

A variable is a token like `{{first_name}}` that DMLY replaces with the real value per
contact at the moment a message sends. In the flow builder, any field that takes them has a
`{}` picker listing the common ones, but the picker is a convenience, not the boundary:
tokens are plain text, and everything on this page works typed by hand.

## The three rules

**1. An unresolvable token becomes empty text.** A message never reaches a contact with a
literal `{{order.number}}` showing, but it can arrive with a hole where the value should
have been: *"Your order  is on its way"*. The builder's canvas preview is the opposite: it
shows unknown tokens as typed, so a clean-looking preview is not proof the token will
resolve. Test by triggering the flow for real.

**2. Most tokens are scoped to an event.** Contact tokens always work. Everything dotted
(`{{invoice.number}}`, `{{appointment.date}}`, `{{order.total}}`) carries data only when the
flow *started from the matching event*, or after a step that fetched it (a **Find Order**
step fills the order tokens, a **Book Meeting** step fills the `meeting.*` ones). An
`{{invoice.number}}` in a plain keyword-reply flow resolves to nothing, every time.

**3. Only flows get the full set.** The event-scoped tokens are a flow-builder feature.
Quick automations, [broadcasts](/broadcasts/create-campaign),
[sequences](/automation/sequences) and Bot Setup replies substitute **contact-level tokens
only**: the Contact, Business and System tables below (all but `{{flow_run_id}}`, which needs
a flow), plus the two live balances. A `{{cart.total}}` in a quick automation's reply is empty
even though the trigger matched a cart.

<Note>
  **Condition steps take no variables at all.** A condition's value box compares literal
  text; it has its own dropdowns for reading trigger data, so don't type `{{tokens}}`
  there.
</Note>

## Contact

Always available, everywhere variables work.

| Token                | Resolves to                                                   |
| -------------------- | ------------------------------------------------------------- |
| `{{first_name}}`     | The first word of the contact's name                          |
| `{{last_name}}`      | The rest of the name                                          |
| `{{name}}`           | The full name                                                 |
| `{{email}}`          | Their email address                                           |
| `{{phone}}`          | Their phone number                                            |
| `{{custom.<field>}}` | One of your [custom fields](/contacts/custom-fields), by name |

<Warning>
  The picker's **Custom field…** entry inserts the placeholder `{{custom.field}}` literally.
  Replace `field` with your real field name (`{{custom.membership_tier}}`), or the token
  resolves against a field actually called "field", which is almost certainly empty.
</Warning>

## Business

| Token               | Resolves to                                      |
| ------------------- | ------------------------------------------------ |
| `{{business_name}}` | Your workspace name, which is your business name |

## System

The picker's **System** group: facts about the conversation rather than about the person. Seven of
the eight work everywhere variables work, like the Contact tokens; only `{{flow_run_id}}` needs a
flow.

| Token                    | Resolves to                                                                                                                                                                                         | Scope                                      |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ |
| `{{last_message}}`       | What the customer last said. Inside a flow it's the message that started the run, refreshed with each reply the contact sends to a question step, which makes it the same value as `{{text}}` there | Anywhere; in a flow, the run's own message |
| `{{contact_id}}`         | The contact's id in DMLY. The same id the [API](/api-reference/introduction) and webhooks use, so a row you write out can be read back later                                                        | Anywhere                                   |
| `{{channel}}`            | The channel type the contact is active on, lowercase and unspaced: `whatsapp`, `facebook`, `instagram`, `telegram`, `sms`, `livechat` and `tiktok` among them                                       | Anywhere                                   |
| `{{channel_name}}`       | The name of that [channel](/channels/overview) in DMLY                                                                                                                                              | Anywhere                                   |
| `{{channel_contact_id}}` | The platform's own id for the person: their WhatsApp number, their Messenger or Instagram id                                                                                                        | Anywhere                                   |
| `{{workspace_id}}`       | Your workspace's id                                                                                                                                                                                 | Anywhere                                   |
| `{{flow_run_id}}`        | The id of this run of this flow, useful as a reference in a webhook payload or a spreadsheet row                                                                                                    | Flows only; empty everywhere else          |
| `{{current_datetime}}`   | Now, as `2026-06-30 10:00:00`, in the contact's timezone (or UTC if you have no timezone for them)                                                                                                  | Anywhere                                   |

These are what you write into a [spreadsheet row](/automation/flow-builder) or an
[HTTP Request](/automation/webhook-step) body when the row has to be matched back to DMLY
afterwards, because a name, phone number or username can be missing today and different tomorrow,
and an id can't.

<Note>
  **`{{last_message}}` outside a flow is a preview, not a promise.** In a broadcast, a sequence step
  or a notification there is no run to read from, so it falls back to the conversation's stored last
  message, which DMLY also updates when *you* send. Mid-conversation it can just as easily be your
  own last reply as the customer's.
</Note>

If a flow saves a variable of its own under one of these names, through an
[HTTP Request step](/automation/webhook-step) or a **Get row from Google Sheets** step, your value
wins and the system one is not used.

## Live balances

These two query the contact's balance at the moment the message sends, in any flow, with no
event scope.

| Token                | Resolves to                                                                                                                 |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `{{credit.balance}}` | Their current [credit balance](/offerings/plans-and-credits). `{{credits.balance}}` is the same token under its other name. |
| `{{loyalty.points}}` | Their current [loyalty points](/finance/coupons#how-loyalty-points-become-a-coupon)                                         |

## Appointments

Filled in flows started by an **appointment event** (booked, confirmed, rescheduled, and the
rest).

| Token                            | Resolves to                                                                                           |
| -------------------------------- | ----------------------------------------------------------------------------------------------------- |
| `{{appointment.service}}`        | The service name                                                                                      |
| `{{appointment.staff}}`          | The staff member's name                                                                               |
| `{{appointment.date}}`           | The date, written out: `Monday, June 30, 2026`                                                        |
| `{{appointment.time}}`           | The time with its timezone: `10:00 AM EDT`                                                            |
| `{{appointment.datetime}}`       | Both together                                                                                         |
| `{{appointment.location}}`       | The service's location                                                                                |
| `{{appointment.link}}`           | The meeting link for an online appointment; where there's no link, it falls back to the location text |
| `{{appointment.status}}`         | The booking status                                                                                    |
| `{{appointment.reschedule_url}}` | The client's own manage page, opened onto rescheduling                                                |
| `{{appointment.cancel_url}}`     | The same page, opened onto cancelling                                                                 |

Dates and times are rendered in the **contact's** timezone, so "10:00 AM" is their 10 AM.

The `meeting.*` spellings (`{{meeting.type}}`, `{{meeting.date}}`, `{{meeting.time}}`,
`{{meeting.datetime}}`, `{{meeting.link}}`, `{{meeting.location}}`, `{{meeting.duration}}`,
`{{meeting.reschedule_url}}`, `{{meeting.cancel_url}}`) are the original family.
`{{appointment.*}}` are aliases of them, plus a few extras (`service`, `staff`, `status`) that
only appointment events carry. In a booking message, meaning a service's **Confirmation
message**, **Reminder message** and **Cancellation message**, and the **Book Meeting** step's
own confirmation, either family resolves.

<Warning>
  **After a Book Meeting step, only `{{meeting.*}}` resolves.** The step writes the meeting
  tokens for the steps that follow it and nothing else, which is what its own panel hint says:
  *"Books on your Google Calendar with a Meet link, then exposes `{{meeting.link}}`,
  `{{meeting.date}}` and `{{meeting.time}}` for later steps."* An `{{appointment.date}}` in a
  step after **Book Meeting** comes out empty. Worse, in a flow an appointment event started,
  it resolves to the appointment that *started* the flow, not the one just booked.
</Warning>

For **class events**:

| Token                               | Resolves to                  |
| ----------------------------------- | ---------------------------- |
| `{{class.name}}`                    | The class name               |
| `{{class.date}}` / `{{class.time}}` | Same formats as appointments |
| `{{class.seats_left}}`              | Seats still open             |

## Finance

Filled in flows started by the matching **Business event**, and by the finance steps that
create the thing mid-flow.

| Token                                | Resolves to                         | Carries data when                                           |
| ------------------------------------ | ----------------------------------- | ----------------------------------------------------------- |
| `{{invoice.number}}`                 | The invoice number, e.g. `INV-0042` | Invoice events; after a create/send-invoice step            |
| `{{invoice.total}}`                  | The invoice total                   | Same                                                        |
| `{{invoice.amount_due}}`             | What's still owed                   | Invoice events                                              |
| `{{invoice.currency}}`               | The currency code                   | Invoice events                                              |
| `{{invoice.status}}`                 | The invoice status                  | Invoice events                                              |
| `{{invoice.due_date}}`               | The due date as `2026-07-30`        | Invoice events                                              |
| `{{payment.amount}}`                 | The payment amount                  | Payment events; after a payment-link or record-payment step |
| `{{subscription.name}}`              | The plan name                       | Subscription events; after a create-subscription step       |
| `{{subscription.price}}`             | The recurring price                 | Subscription events                                         |
| `{{subscription.status}}`            | The subscription status             | Subscription events                                         |
| `{{subscription.next_billing_date}}` | Next billing date, `2026-08-01`     | Subscription events                                         |
| `{{coupon.code}}`                    | The redeemed code                   | **Coupon redeemed** flows only                              |

Amounts are plain numbers: `120.00`, no currency symbol. Put the currency token next to
them yourself: `{{invoice.total}} {{invoice.currency}}`.

## Offerings

| Token               | Resolves to        | Carries data when                                      |
| ------------------- | ------------------ | ------------------------------------------------------ |
| `{{product.name}}`  | The product's name | Stock events; after a send-product or check-stock step |
| `{{product.price}}` | Its price          | Same                                                   |
| `{{product.stock}}` | Units in stock     | Same                                                   |

## E-commerce (Shopify / WooCommerce)

Filled in flows started by a **store event**, and by the FOUND branch of a
[**Find Order** step](/automation/store-automations#find-order) in any flow.

| Token                                     | Resolves to                                                                          |
| ----------------------------------------- | ------------------------------------------------------------------------------------ |
| `{{order.number}}`                        | The store's order number                                                             |
| `{{order.total}}` / `{{order.currency}}`  | The order total and currency                                                         |
| `{{order.status}}`                        | The order's status                                                                   |
| `{{order.items_summary}}`                 | The items in one line: `2× Aurora Hoodie, 1× Trail Socks`                            |
| `{{order.checkout_url}}`                  | The checkout page: the recovery link on an abandoned checkout                        |
| `{{order.tracking_url}}`                  | The tracking link, once the store has one                                            |
| `{{customer.first_name}}`                 | The store customer's first name                                                      |
| `{{customer.total_spent}}`                | Their lifetime spend at the store                                                    |
| `{{product.title}}` / `{{product.price}}` | The product on a stock event                                                         |
| `{{store.name}}`                          | The store's name                                                                     |
| `{{discount.code}}`                       | The code minted by a **Create discount** step earlier in this flow (empty otherwise) |

## WhatsApp cart

Filled in flows started by the **Cart order received** trigger. See
[Sell on WhatsApp](/automation/sell-on-whatsapp). These aren't in the picker; type them.

| Token                                  | Resolves to                                        |
| -------------------------------------- | -------------------------------------------------- |
| `{{cart.total}}` / `{{cart.currency}}` | The cart total and currency                        |
| `{{cart.items_summary}}`               | The items in one line: `2× Blue Shirt, 1× Hat`     |
| `{{cart.item_count}}`                  | How many items                                     |
| `{{cart.note}}`                        | The note the customer attached to the cart, if any |

## Shared location

Filled after a contact shares a location: on the RECEIVED path of a
[**Request Location** step](/automation/sell-on-whatsapp#sending-and-requesting-locations),
or in a flow a shared-location message started. Empty if the contact replied without
actually sharing a pin.

| Token                                              | Resolves to                                         |
| -------------------------------------------------- | --------------------------------------------------- |
| `{{location.latitude}}` / `{{location.longitude}}` | The coordinates, as sent                            |
| `{{location.name}}`                                | The place name, if the contact shared a named place |
| `{{location.address}}`                             | The street address, if one came with it             |

## WhatsApp Flow (form) submissions

Filled when a contact submits a [WhatsApp form](/automation/whatsapp-flows) that a **WhatsApp
Form (Flow)** step sent earlier in the same flow. Empty if the contact replied without submitting
the form, because that step moves on either way. These aren't in the picker; type them.

| Token             | Resolves to                                           |
| ----------------- | ----------------------------------------------------- |
| `{{nfm.<field>}}` | That field's answer, exactly as the form submitted it |

`<field>` is the field's internal name, not the wording of the question you wrote. It's the
small label on the row in the contact's **Flow answers** panel, so send yourself the form once,
submit it, and read the names (and the stored values) off that panel.

Two limits. An answer that arrives as a list of choices resolves to nothing, though the list
itself still shows in **Flow answers**. And a **Condition** step can't test an answer: conditions
compare literal text (see [the three rules](#the-three-rules)). To branch on something, ask for
it with a normal question step, where the reply is captured and can be branched on.

## More tokens you can type

The picker stops at the everyday set; the engine resolves more. The useful ones, same
scoping rules:

| Token                                                                                              | Resolves to                                                                                                                                                                                                                          | Scope                                              |
| -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------- |
| `{{text}}`                                                                                         | The message that triggered the flow, refreshed to the latest reply after each question step. In an [outgoing message](/automation/triggers#outgoing-message-triggers) flow it's the message you or the agent sent, not the contact's | Message-triggered flows                            |
| `{{order.payment_method}}`                                                                         | How the store order is being paid: `cod`, `card`…                                                                                                                                                                                    | Store events, Find Order                           |
| `{{order.tracking_number}}`                                                                        | The bare tracking number                                                                                                                                                                                                             | Store events, Find Order                           |
| `{{order.subtotal}}` / `{{order.first_item_title}}`                                                | Pre-shipping subtotal; the first item's name                                                                                                                                                                                         | Store events                                       |
| `{{customer.name}}` / `{{customer.email}}` / `{{customer.phone}}` / `{{customer.orders_count}}`    | More of the store customer                                                                                                                                                                                                           | Store events (name/phone/email also on cart flows) |
| `{{appointment.duration}}`                                                                         | The length, as `45 min`                                                                                                                                                                                                              | Appointment events                                 |
| `{{class.staff}}` / `{{class.location}}` / `{{class.join_url}}`                                    | More of the class session                                                                                                                                                                                                            | Class events                                       |
| `{{payment.reference}}` / `{{payment.provider}}` / `{{payment.link_url}}` / `{{payment.currency}}` | More of the payment                                                                                                                                                                                                                  | Payment events                                     |
| `{{subscription.billing_interval}}`                                                                | `month`, `year`…                                                                                                                                                                                                                     | Subscription events                                |
| `{{credits.amount}}`                                                                               | The credits moved by this event                                                                                                                                                                                                      | Credits events                                     |
| `{{coupon.amount}}`                                                                                | The redeemed coupon's value                                                                                                                                                                                                          | Coupon redeemed                                    |
| `{{plan.name}}` / `{{plan.price}}`                                                                 | After a send-plan step                                                                                                                                                                                                               | That flow                                          |
| `{{service.name}}` / `{{service.price}}`                                                           | After a send-service step                                                                                                                                                                                                            | That flow                                          |
| `{{lifecycle.stage}}` / `{{lifecycle.old_stage}}`                                                  | The new and previous [pipeline stage](/contacts/pipeline-stages)                                                                                                                                                                     | Lifecycle events                                   |
| `{{ref}}` / `{{growth_tool}}`                                                                      | Which [growth link](/growth/qr-codes-and-links) brought the contact in                                                                                                                                                               | WhatsApp URL trigger                               |
| `{{_last_http_status}}` / `{{_last_http_error}}`                                                   | The status the [HTTP Request step's](/automation/webhook-step) endpoint returned (`0` if there was no response at all), and the reason when there wasn't one                                                                         | After that step, this run                          |
| Your own names                                                                                     | Whatever an [HTTP Request step](/automation/webhook-step) saved under **Save response to fields**, or a [**Get row from Google Sheets** step](/automation/flow-builder) saved under **Save row columns to variables**                | After that step, this run                          |

## Where variables work

Inside a flow, nearly every text a contact ends up seeing takes variables: message bodies
and captions, question steps, button and card text, WhatsApp template parameters, the AI
step's instructions, the [HTTP Request step's](/automation/webhook-step) JSON body, the
**Send SMS** step's message, payment-request amounts and descriptions.

Outside the flow builder (broadcast messages, sequence steps, quick-automation replies, Bot
Setup welcome messages and menu answers), the same `{{syntax}}` works but only the
contact-level set resolves, as covered in [rule 3](#the-three-rules).

<Columns cols={2}>
  <Card title="Build a flow" icon="diagram-project" href="/automation/flow-builder">
    The canvas these tokens live on.
  </Card>

  <Card title="Custom fields" icon="table-list" href="/contacts/custom-fields">
    Create the fields behind the custom tokens.
  </Card>

  <Card title="Store automations" icon="cart-shopping" href="/automation/store-automations">
    The steps that fill the e-commerce tokens.
  </Card>

  <Card title="HTTP Request step" icon="arrow-right-arrow-left" href="/automation/webhook-step">
    Mint your own variables from an API response.
  </Card>
</Columns>
