GoHighLevel Tags vs Custom Fields vs Custom Values: Stop Breaking Your Automations
Most broken workflows in GoHighLevel trace back to the same root cause: operators store data in the wrong container. You build a GoHighLevel tag called “Phone Consultation Scheduled” and stuff the appointment time into it. You hardcode your business phone number into twenty SMS templates. You create a custom field called “First Name” because you didn’t realize the standard field already exists. Then six months later, when you try to filter contacts by appointment time or update your phone number, everything falls apart.
This post walks through how to actually model data in a GHL subaccount so your workflows, SMS templates, emails, and If/Else branches stop failing. We’ll cover tags, contact custom fields, opportunity custom fields, custom values, standard fields, merge-field syntax, and how to clean up a messy account without breaking live automations.

Why Messy Data Breaks Automations
GoHighLevel gives you four places to store information: tags, contact custom fields, opportunity custom fields, and custom values. Each has a specific job. When you use one for another’s purpose, your automations become brittle.
Here’s what happens in practice. You import a snapshot—maybe one of the best GoHighLevel snapshots from the marketplace—and it dumps forty custom fields and sixty tags into your subaccount. Half of them are unused. A quarter are duplicates of standard fields. You don’t clean them up because the workflows still run. Then you build a new workflow six months later, pick the wrong “Phone” field from the dropdown, and wonder why your SMS sends are failing.
Or you create a tag called “Appointment Date: 2024-01-15” because you want to track when someone booked. Now you have three hundred date-specific tags cluttering your tag list, and you can’t filter contacts by “appointments in the last 30 days” without building a workflow that checks three hundred tags. You should have used a date custom field.
Or you hardcode your business phone number into every SMS template and workflow. Then you get a new number, and you have to hunt through fifty workflows, thirty campaigns, and a dozen AI agents to update it. You should have used a custom value.
The fix is simple: use the right container for the job, name it clearly, and wire it into your workflows with merge fields that won’t break when the data changes.
Tags: Boolean Flags for Routing and State
Tags are binary. A contact either has the tag or doesn’t. That’s it. Use them for routing, enrollment, and state management—not for storing facts.
Good tag use cases:
- Pipeline stage enrollment: “Lead Nurture Active”, “Consultation Booked”, “Closed Won”
- Do-not-disturb flags: “DND – Requested Removal”, “DND – Unsubscribed SMS”, “DND – Complained”
- Workflow triggers: “Trigger: Send Welcome Sequence”, “Trigger: Assign to Sales Rep”
- Segmentation: “Attended Webinar”, “Downloaded Lead Magnet”, “Clicked Pricing Link”
- Temporary workflow state: “Processing Payment”, “Waiting for Callback”, “In Onboarding”
Bad tag use cases:
- Storing appointment dates: “Appointment: 2024-01-15” (use a date custom field)
- Storing lead source: “Source: Facebook Ad” (use the standard Source field or a dropdown custom field)
- Storing rep names: “Assigned to John” (use a contact custom field with type User)
- Storing any value that changes frequently or needs to be filtered numerically
When you use tags correctly, your workflow triggers become clean. You add a tag, the workflow fires. You remove a tag, the workflow stops. You can filter contacts by tag presence in seconds. You can bulk-add or bulk-remove tags without touching individual records.
When you use tags incorrectly, you end up with hundreds of tags, no way to query them, and workflows that check for fifty variations of the same concept.
Tag Naming Conventions
Prefix tags by function so they group in the dropdown. Examples:
- DND – [reason]
- Trigger: [workflow name]
- Stage: [pipeline stage]
- Segment: [behavior or attribute]
- Status: [current state]
Avoid vague tags like “Hot Lead” or “Follow Up”. What does “Hot Lead” mean? Who decides? When does it expire? Instead: “Stage: Consultation Booked” or “Trigger: Send Urgency Sequence”.
Delete unused tags. If a tag hasn’t been applied to a contact in six months and isn’t referenced in any workflow, delete it. You can always recreate it if you need it later.
Contact Custom Fields: Facts About People
Contact custom fields store facts about a person. Use them for anything that describes the contact, changes over time, or needs to be displayed in emails, SMS, or the contact record.
GoHighLevel supports these field types:
- Text: free-form short text (names, job titles, notes)
- Text Area: longer text (addresses, multi-line notes)
- Number: integers or decimals (deal size, number of employees, credit score)
- Monetary: currency values (annual revenue, budget, lifetime value)
- Date: calendar dates (appointment date, contract start, birthday)
- Date/Time: timestamps (last contact, next follow-up)
- Dropdown: single-select from a list (lead source, industry, service interest)
- Checkbox: multiple-select from a list (services interested in, communication preferences)
- User: assign a GHL user (sales rep, account manager)
- File Upload: attach documents (contracts, IDs, photos)
Pick the right type. If you create a “Lead Source” field as Text, you’ll end up with fifty variations: “Facebook”, “facebook”, “FB”, “Facebook Ad”, “fb ad”. Use Dropdown instead, define the options once, and every workflow filter works.
If you create an “Appointment Date” field as Text, you can’t filter contacts by “appointments in the next 7 days”. Use Date, and the filter works natively.
Standard Fields vs Custom Fields
GoHighLevel includes standard fields for every contact: First Name, Last Name, Email, Phone, Address, City, State, Postal Code, Country, Source, Tags, Date Added, and more. Do not duplicate these with custom fields.
We see subaccounts with custom fields called “First Name”, “Email Address”, “Phone Number”, “Lead Source”. The operator didn’t realize the standard fields existed, or they imported a snapshot that created duplicates. Now half the workflows use the standard field, half use the custom field, and data syncs break.
Before you create a custom field, check the standard fields. If it already exists, use it. If you need a second phone number (office vs mobile), create a custom field called “Office Phone” or “Mobile Phone”—don’t create “Phone” again.
Field Keys and Merge Fields
Every custom field has a key. The key is the internal identifier GHL uses in merge fields, API calls, and workflow filters. When you create a field called “Appointment Date”, GHL generates a key like “appointment_date” or “contact.appointment_date”.
You reference custom fields in emails, SMS, and workflows with merge-field syntax: {{contact.appointment_date}}. Standard fields use the same syntax: {{contact.first_name}}, {{contact.email}}, {{contact.phone}}.
Merge fields are case-sensitive. {{contact.First_Name}} will not work if the key is first_name. If a field is empty, the merge field renders as blank—no fallback, no error message. Test every merge field before you send it to a real contact.
The official HighLevel overview of merge fields and custom variables covers the full syntax, including opportunity fields, custom values, and date formatting. Bookmark it.

Organizing Fields with Folders
GHL lets you group custom fields into folders. Use this. If you have twenty fields, they all appear in one long dropdown when you build a workflow filter or email template. If you organize them into folders—”Lead Info”, “Appointment Details”, “Service Preferences”, “Internal Notes”—you can find the field you need in two seconds instead of twenty.
Create folders before you create fields. Name them by function, not by alphabet. “Lead Info” is better than “A – Lead Info” because you’re not building a filing cabinet.
When to Use Contact Fields vs Opportunity Fields
Contact custom fields live on the contact record. Opportunity custom fields live on the opportunity (deal) record. If a contact has three opportunities, each opportunity can have different values for the same field.
Use contact fields for facts about the person: industry, company size, lead source, communication preferences, appointment history, lifetime value.
Use opportunity fields for facts about the deal: deal size, close date, service type, contract term, discount applied, sales stage notes.
If you store deal-specific data in contact fields, you can only track one deal at a time. If the contact comes back for a second purchase, you overwrite the first deal’s data. Use opportunity fields, and each deal keeps its own data.
Opportunity fields use the same merge-field syntax: {{opportunity.deal_size}}, {{opportunity.close_date}}. In workflows, you can trigger on opportunity field changes, filter opportunities by field value, and update opportunity fields with actions.
Custom Values: Account-Wide Constants
Custom values are account-level variables. They’re the same for every contact, every opportunity, every workflow. Use them for anything that’s true about your business, not about a specific contact.
Good custom value use cases:
- Business phone number:
- Booking link:
- Support email:
- SMS compliance footer:
- Office address:
- Company name:
- Social media links:
When you use custom values, you update the value once, and every email, SMS, workflow, and AI agent that references it updates automatically. No hunting through fifty templates. No missed updates. No broken links.
When you hardcode values, you create technical debt. Every time your phone number changes, every time you update your booking link, every time you rebrand, you have to touch dozens of assets. And you will miss some. We’ve seen subaccounts with three different phone numbers in circulation because the operator updated the SMS templates but forgot the email campaigns.
Creating and Using Custom Values
Go to Settings > Custom Values. Click Add Custom Value. Give it a name (this becomes the key) and a value. The name should be lowercase, no spaces, underscores for separation: business_phone, booking_url, sms_footer.
Reference it in emails, SMS, and workflows with . Example SMS template:
Hi {{contact.first_name}}, this is . Book your consultation here: . Reply STOP to opt out.
Now when you change your booking URL, you update one custom value, and every SMS that uses it updates instantly.
Custom values also work in workflow If/Else branches, though this is less common. You might check if a contact’s preferred location matches to route them to the right rep.
How Workflows Consume Each Data Type
Understanding how workflows interact with tags, fields, and custom values is critical. Here’s what each workflow component can do:
Triggers
Workflows can trigger on:
- Tag added or removed
- Contact field updated (standard or custom)
- Opportunity field updated
- Opportunity stage changed
- Form submitted (which often updates fields or adds tags)
- Appointment booked, cancelled, or no-showed
- Inbound message, call, or email
You cannot trigger directly on a custom value change because custom values are account-wide, not contact-specific. If you need to trigger a workflow when a custom value changes, you have to manually re-trigger the workflow or use a webhook.
Filters
Workflow filters let you branch logic based on data. You can filter on:
- Tag presence: “Contact has tag X” or “Contact does not have tag X”
- Field value: “Contact field Y equals Z”, “is greater than”, “is less than”, “contains”, “is empty”, “is not empty”
- Opportunity field value: same operators as contact fields
- Date comparisons: “Appointment date is in the next 7 days”, “Last contact was more than 30 days ago”
Filters are where field types matter. If you store a number as text, you can’t filter “greater than 1000”. If you store a date as text, you can’t filter “in the next 7 days”. Use the right field type, and the filters work natively.
Actions
Workflow actions let you modify data:
- Add or remove tags
- Update contact fields (standard or custom)
- Update opportunity fields
- Create or update opportunities
- Send email or SMS (using merge fields from any data type)
- Add to or remove from campaigns
You cannot update custom values from a workflow action because they’re account-wide. You have to update them manually in Settings > Custom Values or via API.
If/Else Branches
If/Else steps in workflows use the same filter logic. You check tag presence, field values, or date comparisons, then route the contact down different paths.
Common mistakes:
- Checking for a tag that was removed earlier in the workflow
- Checking a field that’s empty for most contacts (the Else branch gets 90% of traffic)
- Using “equals” instead of “contains” for text fields (“Facebook Ad” won’t match “facebook ad”)
- Forgetting to handle the Else case (contacts get stuck)
Test every If/Else branch with real data before you turn the workflow on. Create a test contact, set the field values manually, and watch the workflow run. If the contact goes down the wrong path, your filter logic is wrong.
Merge Field Gotchas
Merge fields are simple in theory: {{contact.first_name}} renders as “John”. In practice, they fail silently, render blank, or break templates.
Case Sensitivity
Field keys are case-sensitive. If the key is appointment_date, then {{contact.Appointment_Date}} will not work. GHL won’t throw an error—it’ll just render blank.
To find the exact key, go to Settings > Custom Fields, click the field, and look at the Field Key. Copy it exactly. Or use the merge-field picker in the email or SMS editor—it inserts the correct syntax.
Blank When Empty
If a field is empty, the merge field renders as blank. No fallback, no default value, no “N/A”. Your email says “Hi , welcome to our service” instead of “Hi John, welcome to our service”.
GHL does not have native fallback syntax like {{contact.first_name || 'there'}}. If you need a fallback, you have two options:
- Use a workflow to check if the field is empty, and if so, set a default value before sending the email.
- Write your copy to handle blank fields gracefully: “Hi! Welcome to our service” instead of “Hi {{contact.first_name}}, welcome to our service”.
Date Formatting
Date fields render in ISO format by default: 2024-01-15T00:00:00Z. Not friendly. You can format them with Liquid syntax: {{contact.appointment_date | date: "%B %d, %Y"}} renders as “January 15, 2024”.
Common date formats:
%B %d, %Y: January 15, 2024%m/%d/%Y: 01/15/2024%A, %B %d: Monday, January 15%I:%M %p: 02:30 PM
Test date formatting in a draft email before you send it to contacts. The Liquid syntax is picky about quotes and spacing.
Opportunity Fields in Contact-Level Messages
If you send an email or SMS from a contact-level workflow (not an opportunity-level workflow), and you reference an opportunity field like {{opportunity.deal_size}}, GHL will use the most recent opportunity. If the contact has no opportunities, the field renders blank. If the contact has three opportunities, you get the newest one.
If you need to reference a specific opportunity, trigger the workflow from the opportunity (opportunity stage change, opportunity field update) instead of the contact.
Snapshot Imports and Inherited Junk
When you import a snapshot, you inherit every custom field, tag, and workflow the snapshot creator built. Most snapshots include dozens of unused fields and tags. Some include duplicate standard fields. Some include hardcoded phone numbers and booking links.
Before you build on top of an imported snapshot, audit it:
- Go to Settings > Custom Fields. Open every folder. Delete fields that aren’t used in any workflow, form, or campaign. If you’re not sure, search the field name in the workflow list and campaign list. If it’s not referenced anywhere, delete it.
- Go to Contacts > Tags (or Settings > Tags, depending on your GHL version). Sort by contact count. Delete tags with zero contacts that aren’t referenced in workflows. Delete tags that duplicate field data (“Appointment: 2024-01-15”).
- Go to Automation > Workflows. Open every workflow. Check for hardcoded values in SMS and email templates. Replace them with custom values.
- Check for duplicate standard fields. Search for custom fields called “First Name”, “Last Name”, “Email”, “Phone”, “Source”. If they exist, check if any workflows or forms use them. If yes, update those assets to use the standard fields, then delete the custom fields.
- Check for fields with the wrong type. If “Appointment Date” is a text field, create a new date field, migrate the data with a workflow or CSV export/import, then delete the text field.
This takes an hour for a small snapshot, four hours for a large one. It’s worth it. A clean data model prevents months of debugging broken workflows.
Conversation AI and Agent Studio: Fields vs Values vs Runtime Variables
If you’re building AI agents in GHL (Conversation AI or Agent Studio), you have a fourth data type: runtime variables. These are temporary variables that exist only during a single conversation thread. They’re not stored on the contact record.
Use runtime variables for data the AI collects during the conversation but doesn’t need to persist: the user’s current question, a temporary calculation, a menu choice. Use contact custom fields for data you want to store permanently: the user’s service interest, appointment preference, budget range.
AI agents can read contact fields and custom values with the same merge-field syntax: {{contact.first_name}}, . They can also update contact fields with actions during the conversation.
Common mistake: storing every piece of conversation data in contact fields. If the AI asks “What’s your favorite color?” as part of a quiz, you don’t need a “Favorite Color” contact field unless you’re going to use it in future workflows. Use a runtime variable, let the AI respond, and move on.
Another mistake: hardcoding your booking link or phone number in the AI prompt. Use custom values instead. When you update the custom value, every AI agent updates automatically.
Naming Conventions and Cleanup Playbook
Consistent naming prevents confusion. Here’s a naming system that works:
Tags
- Prefix by function:
DND -,Trigger:,Stage:,Segment:,Status: - Use title case:
Stage: Consultation Booked, notstage: consultation booked - Avoid abbreviations unless they’re universal:
DNDis fine,CBfor “Consultation Booked” is not
Custom Fields
- Use title case:
Appointment Date,Lead Source,Service Interest - Avoid redundant words:
Appointment Date, notContact Appointment Date(it’s already a contact field) - Use folders to group related fields:
Lead Info / Lead Source,Lead Info / Industry,Lead Info / Company Size - For yes/no questions, use a dropdown with “Yes” and “No” options, not a checkbox (checkboxes are for multi-select)
Custom Values
- Use lowercase with underscores:
business_phone,booking_url,sms_footer - Be specific:
primary_office_phoneandsupport_phone, notphone_1andphone_2
Cleanup Playbook for a Messy Subaccount
If you inherit a subaccount with 80 custom fields, 200 tags, and workflows that reference half of them, here’s how to clean it up without breaking everything:
- Export a list of all workflows, campaigns, forms, and AI agents. You’ll need this to search for field and tag references.
- Go to Settings > Custom Fields. For each field, search its name in the workflow list, campaign list, and form list. If it’s not referenced anywhere and has no data (or only a few contacts with data), delete it. If it has data but isn’t referenced, export the data to CSV, then delete the field.
- Go to Contacts > Tags. For each tag, check the contact count. If it’s zero and the tag isn’t referenced in workflows, delete it. If it’s non-zero, check if the tag is used for routing or just historical. If historical, remove it from all contacts and delete it.
- Check for duplicate standard fields. Search for custom fields called “First Name”, “Last Name”, “Email”, “Phone”, “Source”, “Address”, “City”, “State”, “Zip”. If they exist, check if any workflows or forms use them. If yes, update those assets to use the standard fields. If the custom field has data the standard field doesn’t, migrate the data with a workflow (If contact.custom_first_name is not empty and contact.first_name is empty, update contact.first_name to contact.custom_first_name). Then delete the custom field.
- Check for fields with the wrong type. If “Appointment Date” is text, “Lead Source” is text, or “Deal Size” is text, create new fields with the correct type, migrate the data, update workflows and forms to use the new fields, then delete the old fields.
- Check for hardcoded values in SMS and email templates. Search for phone numbers, URLs, and email addresses. Replace them with custom values.
- Organize remaining fields into folders. Create folders by function (Lead Info, Appointment Details, Service Preferences, Internal Notes), then move fields into the appropriate folder.
- Rename vague fields. If you have a field called “Notes”, rename it to “Sales Notes” or “Onboarding Notes” or “Support Notes” depending on what it’s used for. If you have a field called “Status”, rename it to “Lead Status” or “Onboarding Status”.
- Test every workflow. Create a test contact, set field values manually, and watch the workflow run. Make sure tags are applied correctly, fields are updated correctly, and emails render correctly.
This process takes a full day for a moderately messy subaccount. It’s not fun. But it’s the only way to prevent future breakage.
What to Build First When Starting from Zero
If you’re building a new subaccount from scratch, here’s the order to build your data model:
- Define your lead sources. Create a contact custom field called “Lead Source” with type Dropdown. Add your sources: Google Ads, Facebook Ads, Referral, Website Form, Cold Outreach, etc. Make sure every form and workflow that creates a contact sets this field.
- Define your pipeline stages. Create tags for each stage:
Stage: New Lead,Stage: Contacted,Stage: Consultation Booked,Stage: Proposal Sent,Stage: Closed Won,Stage: Closed Lost. Build workflows that add and remove these tags as contacts move through the pipeline. - Create custom values for business constants. At minimum:
business_phone,booking_url,support_email,sms_footer. Use these in every SMS and email template. - Create contact fields for facts you’ll use in workflows. Examples:
Service Interest(dropdown),Appointment Date(date),Assigned Rep(user),Budget Range(dropdown or monetary),Last Contact Date(date). Don’t create fields you won’t use. - Create DND tags. At minimum:
DND - Unsubscribed SMS,DND - Unsubscribed Email,DND - Requested Removal. Add workflow filters to every SMS and email workflow that checks for these tags and stops the workflow if present. - Create folders for custom fields. Group fields by function: Lead Info, Appointment Details, Service Preferences, Internal Notes. Move fields into folders as you create them.
- Build your first workflow. Start simple: form submission → add tag → send email → wait 2 days → send follow-up email. Test it with a real contact. Make sure the tag is applied, the emails render correctly, and the merge fields work.
- Build your second workflow. Add complexity: form submission → check lead source → if Google Ads, add tag and assign to Rep A → if Facebook Ads, add tag and assign to Rep B → send email with rep’s booking link. Test it with two contacts, one from each source.
- Iterate. Add fields and tags as you need them, not before. Every field you create is a field you have to maintain. Every tag you create is a tag that clutters the dropdown. Build lean.
How to Test a Field Before You Hang a Workflow On It
Before you build a workflow that triggers on a field update or filters on a field value, test the field:
- Create a test contact. Go to Contacts > Add Contact. Use a fake name and your own email/phone so you can see the messages.
- Manually set the field value. Open the contact, scroll to the custom field, and enter a value. If it’s a dropdown, pick an option. If it’s a date, pick a date. If it’s a number, enter a number.
- Check the merge field. Go to Conversations, send a manual SMS to the test contact, and include the merge field:
Your appointment is on {{contact.appointment_date}}. Send it. Check your phone. Does the date render correctly? Is it formatted the way you want? If not, fix the field type or add Liquid formatting. - Build a simple workflow. Trigger: contact field updated (the field you’re testing). Action: add a tag called “Test – Field Updated”. Turn the workflow on. Go back to the test contact, change the field value, and save. Wait 30 seconds. Refresh the contact record. Is the tag applied? If yes, the trigger works. If no, the field key is wrong or the workflow isn’t on.
- Test a filter. Build a workflow with an If/Else step. Condition: contact field equals the value you set. If true, add tag “Test – Filter True”. If false, add tag “Test – Filter False”. Manually trigger the workflow on the test contact (or use a tag-added trigger). Check which tag is applied. If it’s the wrong one, your filter logic is wrong.
- Test with empty data. Clear the field value on the test contact. Trigger the workflow again. Does it handle the empty field gracefully? Or does it break? If it breaks, add a filter to check if the field is not empty before you use it.
- Delete the test contact and test tags. Once you’ve confirmed the field works, delete the test contact and remove the test tags from your tag list. Don’t leave test data in production.
This process takes five minutes per field. It prevents hours of debugging later.
Final Thoughts
Most GHL automation failures trace back to data modeling mistakes made in the first week of building a subaccount. You create a tag when you need a field. You hardcode a value when you need a custom value. You duplicate a standard field because you didn’t know it existed. Six months later, you’re debugging workflows that fail silently because the data isn’t where the workflow expects it.
The fix is simple: use tags for boolean state, contact fields for facts about people, opportunity fields for facts about deals, and custom values for business constants. Name them clearly. Organize them into folders. Test them before you build workflows on top of them. Clean up imported snapshots before you start building.
If you’re inheriting a messy subaccount or need help building a clean data model from scratch, we build and maintain GHL subaccounts at SumoGrowth. But most operators can do this themselves with a few hours of focused work and a clear plan.
Build your data model right the first time, and your workflows will run for years without breaking. Build it wrong, and you’ll spend more time debugging than building.
By Darius Gaynor




