Few things sting quite like watching a live SMS blast go out with Hey {{contact.first_name}}! right in the opener because a merge tag failed to resolve. I’ve debugged dozens of broken client automations where an empty custom field or a mismatched trigger context dumped raw Liquid markup straight into a lead’s inbox.
Getting dynamic data right in GoHighLevel comes down to knowing where the data lives, what the workflow runtime actually has access to, and writing proper fallbacks so missing values don’t break your copy. Here’s a breakdown of the exact syntax across email and SMS, along with the common pitfalls that cause tags to break.

The Core Merge Field Categories in HighLevel
Merge fields in HighLevel pull stored values from your CRM database and inject them into outgoing messages at runtime. While the visual editor gives you a dropdown picker, writing workflow copy directly or building complex templates requires understanding the underlying data scopes.
Dynamic data in HighLevel is split across five distinct scopes:
- Contact (
{{contact.*}}): Lead-level data like first name, email, phone number, and any custom fields mapped to the contact record. - User (
{{user.*}}): Details for the assigned team member handling the contact or executing the workflow step. - Location (
{{location.*}}): Sub-account metadata, including your agency or client’s business name, phone number, address, and primary email. - Custom Values (
{{custom_values.*}}): Global variables created under Settings > Custom Values that apply account-wide. If you need to set these up, check out our guide on how to create and use custom values in GoHighLevel. - Workflow Trigger Context (
{{appointment.*}},{{order.*}},{{invoice.*}}): Ephemeral data tied strictly to the event that kicked off the current workflow run.
Standard Merge Tag Syntax for Email and SMS Workflows
HighLevel uses a double curly-bracket syntax rooted in Liquid templates. When you write raw copy in an SMS action or drop custom HTML into an email template, you can paste these tokens directly into your text.
Here are the standard contact and account tokens you’ll use most often:
// Contact details
{{contact.first_name}}
{{contact.last_name}}
{{contact.name}}
{{contact.email}}
{{contact.phone}}
{{contact.company_name}} // Location (Sub-account) details
{{location.name}}
{{location.phone}}
{{location.email}}
{{location.website}}
{{location.address}} // Assigned staff user details
{{user.first_name}}
{{user.last_name}}
{{user.email}}
{{user.phone}}Custom contact fields follow a normalized slug based on the field name you entered under Settings > Custom Fields. If you created a field called “Property Address”, HighLevel turns that into {{contact.property_address}}. If you’re building custom webhooks or parsing raw payloads, you can inspect the exact keys in the GoHighLevel API documentation.
Setting Default Fallback Text with Liquid Syntax
What happens when someone opts in through a form with just an email address and skips the name field? By default, HighLevel sends an empty string or leaves an awkward space. An SMS that says “Hey , your quote is ready” immediately gives away that an unpolished bot sent it.
You can solve this cleanly by adding a Liquid default filter inside the merge tag. HighLevel’s workflow engine supports standard Liquid filters on both contact and custom fields.
Here’s how to structure your fallback values:
// Fallback for first name in an SMS
Hey {{ contact.first_name | default: 'there' }}, // Fallback for company name
Thanks for reaching out to {{ contact.company_name | default: 'your team' }}. // Fallback for assigned user signature
Best regards,
{{ user.name | default: location.name }}If contact.first_name holds “Sarah”, the message renders “Hey Sarah,”. If that field is null or whitespace, it renders “Hey there,”. That single filter prevents broken grammar across every outbound sequence you build.
Why Trigger-Specific Merge Fields Break (And How to Fix Them)
The single most common merge tag bug I see is trying to use event-specific tokens in a workflow triggered by the wrong event. If you paste {{appointment.start_time}} into an SMS inside a workflow triggered by “Contact Tag Added”, it will always resolve to blank.
HighLevel only populates contextual objects when the incoming webhook or event payload actually contains that data:
Merge ObjectRequired Workflow TriggerExample Tag{{appointment.*}}Customer Booked Appointment / Appointment Status{{appointment.start_time}}{{order.*}}Order Submitted / Payment Received{{order.formatted_amount}}{{invoice.*}}Invoice Sent / Invoice Paid{{invoice.due_date}}{{inbound_webhook.*}}Inbound Webhook Trigger{{inbound_webhook.custom_key}}
If you need appointment data inside a generic workflow (like a nurture sequence started days later), you can’t rely on the {{appointment.*}} scope. You need to write that appointment date into a dedicated Contact Custom Field during the initial booking step, or pass it downstream via webhooks. If you’re working with outside data sources, see our guide on how to send custom values and contact fields in GoHighLevel webhooks.
Using Merge Fields in HTML Email Templates
Inside HighLevel’s visual Drag & Drop Email Builder, merge fields get parsed through the template compiler before the final MIME package is assembled. You can drop merge tags directly into raw HTML blocks, paragraph text, or button links.
Here is an example showing dynamic greeting logic and contact-specific tracking parameters on a CTA link:
Hi { { contact.first_name | default: 'there' }
},
We have updated your account profile for { { contact.company_name | default: 'your organization' }
}
.
Sent by { { user.name | default: location.name }
}
•
{ { location.address }
}
Passing contact tokens inside button destination URLs (like ?email={{ contact.email }}) lets you pre-populate forms and maintain attribution across your funnels. If your contacts originate from paid campaigns, make sure your incoming fields line up by checking our walkthrough on mapping Facebook Lead Ads to custom fields in GoHighLevel.
Character Count and GSM-7 Gotchas with SMS Merge Fields
In emails, dynamic text length rarely breaks anything. In SMS, it directly impacts your billing and deliverability. Standard SMS runs on GSM-7 encoding, giving you 160 characters per segment.
When a merge field resolves, it expands to the exact character count of whatever is stored in the database. If your draft SMS is 155 characters long and you inject {{contact.first_name}}, a recipient named “Christopher” instantly pushes your text into a 2-segment message (billed at concatenated rates of 153 characters per segment).
Keep these SMS rules in mind when drafting copy:
- Account for name variance: Always budget at least 12–15 characters of buffer for name and custom field substitutions.
- Watch for hidden UCS-2 switches: Pasting special quotes, em-dashes, or emojis directly next to tags can force the carrier to encode via UCS-2, slashing your segment limit from 160 characters down to 70. You can review character constraints in the official HighLevel Help Portal.
- Never test with just short names: Don’t just run a test with “Dan”. Test with longer names and edge-case custom field values to make sure your segment count stays where you expect.
Step-by-Step: Testing Workflow Merge Fields Before Going Live
Don’t assume your merge fields will resolve cleanly in production. Follow this quick routine inside your sub-account before turning any campaign on:
- Create an edge-case test contact: Head to Contacts > Add Contact. Use your own mobile number and email, but deliberately leave a few custom fields blank to make sure your Liquid fallbacks kick in properly.
- Run the workflow test tool: Open your workflow, hit Test Workflow in the top-right corner, and select your test contact.
- Inspect the Execution Logs: Go to Workflows > Execution Logs and open the specific run for your test contact. HighLevel prints the exact parsed string under the step details so you can see what the template engine produced before it left the server.
- Check the physical message: Confirm the SMS or email landed on your phone or inbox without unparsed brackets, awkward whitespace, or broken link strings.
Frequently Asked Questions
Why are my merge fields showing up as raw curly brackets in sent messages?
This almost always points to a typo in the field key (like writing {{contact.firstname}} instead of {{contact.first_name}}) or using a scoped token (like {{appointment.start_time}}) inside a workflow that wasn’t triggered by an appointment event. If HighLevel can’t find the key in memory, it outputs the raw tag text.
Can I use custom value merge tags inside custom email templates?
Yes. Custom Values ({{custom_values.variable_name}}) work across email templates, workflow SMS steps, review requests, and funnels. They resolve globally for the sub-account.
How do I format dates for merge fields in GoHighLevel?
Standard appointment and date custom fields render using the timezone configured under Settings > Business Profile. If you’re ingesting dates via custom webhooks, format the timestamp to your desired human-readable format before sending it into HighLevel.
Do merge tags work inside workflow ‘Internal Notification’ steps?
Yes. Internal notification steps (SMS, email, or in-app alerts sent to assigned team members) support all contact, location, and trigger merge tags so your team gets full context on new leads.
Next Steps for Your Workflows
Once you have your tags and fallback logic locked down, make sure your outbound messages only send when leads are awake. If you haven’t set up delivery constraints yet, check out our guide on setting up SMS quiet hours in GoHighLevel workflows.

