Merge fields: the {{…}} placeholders that personalise pre-written messages

Drop contact data into fixed-mode triggers, follow-ups, voice openers, fallback lines, and the Advanced business-context glossary.

Updated June 26, 2026

Merge fields let you write one message template and have it render
differently per contact. Type {{contact.first_name}} in a follow-up,
and at send time it becomes "Hi Jamie" or "Hi Alex" depending on whose
number is on the other end.

Where they work

Anywhere you're writing a pre-written template — not anywhere the
AI composes its own reply.

  • Fixed-mode trigger messages (Triggers tab, when "Fixed message"
  • Follow-up steps (Follow-ups tab — each step's message body)
  • Voice opener / closer / end-of-call phrase (Voice tab)
  • Fallback message (Settings tab, when behaviour is "Send a
  • Widget welcome message (Widget config)
  • Qualifying question text (Qualifying tab)
  • Business Context glossary on Advanced agents

The AI's own replies don't need merge fields because the agent already
sees the contact data and personalises naturally. Writing
{{contact.first_name}} in the system prompt is usually redundant.

Syntax

{{token}}                       → empty string if missing
{{token|fallback text}}         → "fallback text" if missing

The token path uses dots: {{namespace.key}}. The optional |fallback
after a pipe renders in place of an empty or missing value.

Always use a fallback on anything that might be empty. First names,
custom fields, and calls from unknown numbers can all hit a blank.
{{contact.first_name|there}} reads naturally in both cases.

The tokens

Contact

  • {{contact.first_name|fallback}} — first name, or extracted from
  • {{contact.last_name|fallback}} — last name
  • {{contact.full_name|fallback}} — whole name
  • {{contact.email|fallback}} / {{contact.phone|fallback}}
  • {{contact.company|fallback}} / {{contact.city|fallback}} /
  • {{contact.tags|fallback}} — comma-joined list

Custom fields (contact-level only)

{{custom.<fieldKey>|fallback}} resolves against the contact's GHL
custom fields. The <fieldKey> is the stable slug from Settings →
Custom Fields (usually contact.your_field_name in GHL). The
{{…}} Insert value picker pre-populates with the real field keys
from your location so you don't have to type them.

Note: There's no {{opportunity.*}} or {{custom.vehicle_color}}
for opportunity-level custom fields — opportunities can be multiple per
contact (which one would merge?). See Advanced agents
for how the AI reads opportunity data directly instead.

Agent

  • {{agent.name|fallback}} — the agent's display name (or persona

Assigned user (contact's CRM owner)

The team member assigned to the contact in GHL. Requires the OAuth
scope users.readonly — reconnect GHL from Integrations if the
values come back empty. Useful for "your rep is Alex at
+1 415 555 0100" style templates.

  • {{user.name|our team}} — full name
  • {{user.first_name|fallback}} / {{user.last_name|fallback}}
  • {{user.email|fallback}}
  • {{user.phone|fallback}}
  • {{user.extension|fallback}}

Date

  • {{date.today}} — locale-friendly like "Saturday, November 8"
  • {{date.tomorrow}} — same, next day
  • Respects the agent's timezone if set (Working Hours tab)

Worked examples

Fixed-mode trigger message, tag-added event:

Hi {{contact.first_name|there}}, thanks for reaching out about
{{custom.service_interest|our services}}. I'm {{agent.name|from the
team}}. Quick question — what's got you looking right now?

Follow-up step, "schedule a chat":

Hey {{contact.first_name|there}}, looping back —
{{user.name|our team}} has some availability
{{date.tomorrow}}. Want me to lock in a time?

Voice call opener:

Hi {{contact.first_name|there}}, this is {{agent.name|calling from}}
about your inquiry. Got a few minutes?

The Insert-value picker

Every merge-aware textarea has a {{…}} Insert value button in the
top-right corner. Click it to get a grouped, searchable list of every
token available — built-ins, your CRM custom fields (auto-fetched),
and a link to this reference page. Typing into the search box filters
live; Enter inserts the top match at the cursor.

How it works at send time

When a pre-written message is about to send, we:

  1. Load the contact record (name, fields, tags)
  2. Hydrate contact custom fields (match fieldKey to your tokens)
  3. Resolve the assigned user if any {{user.*}} tokens are used
  4. Substitute every {{token}} in the template
  5. Send the result

If any step fails (e.g. GHL scope missing, contact deleted), the
affected tokens fall back to their |fallback value or render as
empty — the message still sends. No half-rendered templates.

Common mistakes

  • Using tokens in AI instructions — the agent already has the
  • Forgetting fallbacks"Hi {{contact.first_name}}," on an
  • Assuming {{user.*}} works without the scope — if you added
  • Using {{custom.*}} for opportunity data — opportunity-level
Merge fields: the {{…}} placeholders that personalise pre-written messages — Xovera Help