You build a form, drop it on a landing page, wire up a workflow, and test it—only to realize half the form inputs never reach the contact record. First Name and Email update fine, but custom dropdowns and text answers simply vanish into the submission log void.
I’ve run into this exact issue dozens of times across client sub-accounts. Here is how to reliably map both native form submissions and inbound webhook payloads into custom fields using GoHighLevel workflows without dropping data.

The Core Reason Form Data Fails to Map
HighLevel handles form data in two very different ways depending on where the data originates. With native HighLevel forms, fields only auto-populate if the input was explicitly tied to an existing custom field when you placed it on the canvas. If someone created a generic standalone field inside the form builder, HighLevel treats the response as raw submission metadata rather than a contact property update.
If you’re pushing submissions from external sources—Elementor, Typeform, Webflow, or a custom HTML form over a webhook—HighLevel won’t guess your schema. You have to explicitly map the payload keys to contact fields inside a workflow using the Update Contact Field action.
Before touching the workflow engine, you need to make sure your custom fields actually exist and use matching types.
Step 1: Define Custom Fields with Matching Data Types
Head to Settings > Custom Fields in your sub-account. HighLevel enforces strict type validation. If your form sends text into a field typed as a Number or Date, the workflow step will fail silently or just leave the field empty without an obvious error.
Here are the common field types you’ll configure:
- Single Line Text: Good for job titles, company names, or generic short answers.
- Number: For budget values, team sizes, or headcount (strip symbols like commas and dollar signs first).
- Dropdown / Radio: Form values must match HighLevel choices character-for-character, including casing and spacing.
- Checkbox (Multiple): Great for multi-select options like requested service categories.
Note down the exact field key (like contact.budget_range). If you need a refresher on variable syntax, check our guide to custom values and custom fields in GoHighLevel webhooks.
Step 2: Build the Workflow Trigger
Go to Automation > Workflows and create a fresh workflow from scratch. Your trigger setup depends on where the form lives.
Option A: Using a Native HighLevel Form
Select the Form Submitted trigger. Always add a filter for Form is and pick your specific form. If you skip the filter, the workflow runs on every single form submission across the entire sub-account, creating major mapping headaches.
Option B: Using an Inbound Webhook
If your form is on an external site, pick the Inbound Webhook trigger. Copy the generated webhook URL and set it as your form’s POST destination.
Fire a live test submission from your form so HighLevel can listen and map out the incoming JSON schema. Here’s a clean sample payload structure:
{ "first_name": "Sarah", "last_name": "Connor", "email": "sarah@example.com", "phone": "+15550192834", "company_size": "50-100", "monthly_ad_spend": 15000, "service_requested": "PPC Management"
}Once HighLevel catches the test payload, click Save Trigger. If nothing comes through, run through our checklist for GoHighLevel workflow webhooks not firing.
Step 3: Map Submissions Using the Update Contact Action
Now that the trigger holds the payload, map the incoming values to the contact. Click the + icon under your trigger and add the Update Contact Field action.
- Choose your target Custom Field (for example, Monthly Ad Spend).
- Click the field’s value input and open the dynamic merge tag selector.
- For native forms: select Form Submission > [Your Form Field Name].
- For inbound webhooks: select Inbound Webhook > [Payload Key] (e.g.,
{{inboundWebhook.monthly_ad_spend}}).
You can bundle multiple field assignments into a single action card or chain them separately if you want clear visual checkpoints in the builder.
If some fields are optional and you want clean fallback values instead of empty gaps, see our breakdown on GoHighLevel merge fields and fallback values.
Handling Data Transformations with Custom Code
Raw form data is messy. Users submit phone numbers with parentheticals and spaces like (555) 019-2834, or type $15,000 when your custom field expects a raw integer. If you map messy data directly, HighLevel rejects the update.
Drop a Custom Code action (Node.js) between the trigger and the Update Contact step to sanitize everything first.
// Clean and format incoming form payload
const rawSpend = inputData.monthly_ad_spend || "0";
const cleanSpend = parseInt(rawSpend.replace(/[^0-9]/g, ""), 10);
const rawPhone = inputData.phone || "";
const cleanPhone = rawPhone.replace(/D/g, "");
const formattedData = { normalized_spend: cleanSpend, e164_phone: cleanPhone.length === 10 ? `+1${cleanPhone}` : cleanPhone, full_name: `${inputData.first_name || ""} ${inputData.last_name || ""}`.trim()
};
output = formattedData;In the subsequent Update Contact Field step, map {{customCode.normalized_spend}} and {{customCode.e164_phone}} instead of the raw webhook keys. This eliminates validation failures before they happen.
If you’re pulling leads directly from paid ad forms, check out our guide on how to connect Facebook Lead Ads to GoHighLevel and map custom fields.
The Gotcha: Overwriting vs. Appending Multi-Select Fields
Here’s a common trap: multi-select checkboxes. When an existing contact fills out a secondary form with a new checkbox selection, the standard Update Contact Field action overwrites their previous selections entirely instead of appending the new choice.
If you need to keep historical selections intact, handle it like this:
- Add an If/Else branch checking whether the custom field is already populated.
- If empty, run the standard Update Contact Field action.
- If it already contains values, run a Custom Code block to merge the existing string array with the incoming array, deduplicate it, and update the contact via the HighLevel REST API.
For more details on endpoint payloads, consult the official HighLevel Help Portal on workflow triggers and actions.
Testing the Workflow Execution
Never mark a workflow as done without checking the execution logs directly:
- Switch the workflow toggle from Draft to Publish and hit Save.
- Open your form in an incognito window and submit a full test entry with data in every single field.
- Go to Automation > Workflows > [Your Workflow] > Execution Logs.
- Click the newest execution run (they usually process in 400ms to 900ms).
- Inspect the output under Update Contact Field to verify values matched your input.
- Open Contacts, pull up the test contact record, and check the custom fields in the left sidebar to confirm they updated.
Frequently Asked Questions
Why are my custom fields blank after the workflow runs successfully?
This almost always points to a typo in the merge tag or a type mismatch. Check your Execution Logs at the trigger step. If the trigger property is null or missing, your merge tag key doesn’t match the incoming JSON payload key.
Can I map files or PDF uploads from forms to custom fields?
Yes. Native form file uploads save the URL automatically. For external webhook forms, make sure your form handler uploads the file to S3 or cloud storage first, then sends the public URL as a string in the payload. Map that URL to a Single Line Text custom field.
Do native HighLevel forms need a workflow to map custom fields?
No, provided the field on the form was added directly from your existing Custom Fields list during form creation. You only need a workflow if you are transforming data, updating related custom fields conditionally, or handling external third-party webhooks.
Next Steps
Once your form data maps cleanly to custom fields, you can use those values across SMS templates, email campaigns, and conditional logic branches. If you need to push these custom fields downstream into an external database or CRM, read our guide on finding and using client Location IDs in GoHighLevel API integrations.

