How to Track UTM Parameters in Salesforce Forms
A Complete GTM Guide for Clean Attribution
If you’re running paid campaigns or multi-channel marketing, your success depends on one thing: knowing exactly where your leads come from. Yet most Salesforce setups miss this completely. Leads show up — but attribution is broken. Campaigns look like “direct.” ROI becomes guesswork. The fix? Proper UTM tracking — implemented correctly, end to end.
In This Guide
You’ll learn how to:
- Capture UTM parameters reliably from any traffic source
- Send them to Salesforce via hidden form fields
- Avoid the most common attribution mistakes
- Choose the right storage strategy (sessionStorage vs. cookies)
- Implement everything cleanly using Google Tag Manager (GTM)
What Are UTM Parameters?
UTM parameters are query string tags added to URLs to track the origin and performance of your marketing traffic. When a user clicks a tagged link, these parameters are passed in the URL and can be captured by your analytics tools — and critically, by your CRM.
Example URL
https://example.com/?utm_source=google &utm_medium=cpc &utm_campaign=spring_sale &utm_term=crm+software &utm_content=banner_v1
Here’s what each parameter tracks:
| Parameter | Purpose | Example Value |
|---|---|---|
| utm_source | Where traffic came from | google, linkedin, newsletter |
| utm_medium | The marketing channel | cpc, email, social, organic |
| utm_campaign | Campaign name or promotion | spring_sale, q4_launch |
| utm_term | Paid keyword (optional) | crm+software, best+tool |
| utm_content | Ad variation / creative (optional) | banner_v1, cta_blue |
When captured correctly, these allow you to tie every lead in Salesforce back to its precise origin — whether that’s a Google ad, a LinkedIn post, or an email campaign.
Why Most Salesforce UTM Tracking Fails
The most common approach developers take follows this flawed logic:
This seems reasonable but leads to data contamination. Here’s a real example:
utm_source = google utm_medium = cpc utm_campaign = spring_sale utm_term = crm+software utm_content = banner_v1
utm_source = linkedin utm_medium = paid-social (no campaign/term/content)
Result with broken logic — contaminated data:
source = linkedin ✓ correct medium = paid-social ✓ correct campaign = spring_sale ✗ WRONG — from previous session term = crm+software ✗ WRONG — from previous session content = banner_v1 ✗ WRONG — from previous session
You’ve now mixed two separate campaigns into a single lead record — making it impossible to accurately attribute the conversion.
The Correct Attribution Logic
A “new campaign visit” is detected when the URL contains any of: utm_source, utm_medium, or utm_campaign.
The result of correct logic:
- New values → stored
- Missing values → blank (not inherited from previous visit)
- Old values → always cleared on new campaign visit
- No cross-contamination between campaigns
This is the single most important principle in this entire setup. Everything else flows from it.
Choose Your Storage Strategy
Before writing any GTM code, decide where UTM values will be stored between page views. This decision affects your entire implementation and depends on your sales cycle.
| Criteria | SessionStorage | Cookies |
|---|---|---|
| Persistence | Ends when tab/session closes | Configurable (e.g. 30–90 days) |
| Cross-session tracking | No | Yes |
| Implementation complexity | Simple | Medium |
| Risk of stale data | Low | Medium |
| Consent requirements | Minimal | Often required (GDPR/CCPA) |
| Best use case | Same-session conversions | Long sales cycles |
Use SessionStorage when:
- Users typically convert in the same browsing session
- You want the cleanest, simplest implementation
- You want attribution to automatically reset on a new session
- Your product has a short consideration cycle (e.g., SaaS free trial, demo request)
Use Cookies when:
- Your sales cycle spans multiple days or sessions
- Users research before returning to convert
- You need to support multi-session attribution
- You have a consent management platform (CMP) in place for GDPR/CCPA compliance
Add Hidden Fields to Your Form
Your lead capture form must include hidden input fields for each UTM parameter. These fields will be populated by JavaScript and submitted alongside the visible form fields into Salesforce.
Add this HTML inside your form element:
<input type="hidden" name="utm_source" id="utm_source"> <input type="hidden" name="utm_medium" id="utm_medium"> <input type="hidden" name="utm_campaign" id="utm_campaign"> <input type="hidden" name="utm_term" id="utm_term"> <input type="hidden" name="utm_content" id="utm_content">
Capture & Store UTMs in GTM
Create a Custom HTML Tag in GTM with the following code. Set the trigger to fire on All Pages so UTMs are captured on the very first page load — before the user navigates anywhere.
Option A: SessionStorage (Recommended)
(function() { var keys = [ 'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content' ]; var params = new URLSearchParams(window.location.search); // Detect a new campaign visit var hasNewAttribution = params.has('utm_source') || params.has('utm_medium') || params.has('utm_campaign'); if (hasNewAttribution) { // Overwrite ALL keys — even absent ones get cleared keys.forEach(function(key) { sessionStorage.setItem(key, params.get(key) || ''); }); } })();
Option B: Cookies (Multi-session)
(function() { function setCookie(name, value, days) { var d = new Date(); d.setTime(d.getTime() + (days * 24 * 60 * 60 * 1000)); document.cookie = name + '=' + encodeURIComponent(value) + ';path=/;expires=' + d.toUTCString() + ';SameSite=Lax'; } var keys = [ 'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content' ]; var params = new URLSearchParams(window.location.search); var hasNewAttribution = params.has('utm_source') || params.has('utm_medium') || params.has('utm_campaign'); if (hasNewAttribution) { keys.forEach(function(key) { setCookie(key, params.get(key) || '', 90); // 90-day window }); } })();
Create GTM Variables
For each UTM field, create a Custom JavaScript Variable in GTM. These variables will be referenced in your form populate tag.
Create 5 variables named: JS - UTM Source, JS - UTM Medium, JS - UTM Campaign, JS - UTM Term, JS - UTM Content
Each variable follows this pattern (example for utm_source):
// Variable name: JS - UTM Source function() { return sessionStorage.getItem('utm_source') || ''; }
For the cookie-based implementation, use:
// Variable name: JS - UTM Source (cookie version) function() { var match = document.cookie.match( new RegExp('(?:^|; )utm_source=([^;]*)') ); return match ? decodeURIComponent(match[1]) : ''; }
Populate Hidden Form Fields
Create a second Custom HTML Tag to read the GTM variables and inject them into your form’s hidden fields. This must fire AFTER the DOM has loaded.
Tag settings: Type → Custom HTML | Trigger → DOM Ready (form page only, e.g. /contact or /demo)
(function() { function setField(name, value) { var el = document.querySelector('input[name="' + name + '""]'); if (el) { el.value = value || ''; } } function populate() { setField('utm_source', {{JS - UTM Source}}); setField('utm_medium', {{JS - UTM Medium}}); setField('utm_campaign', {{JS - UTM Campaign}}); setField('utm_term', {{JS - UTM Term}}); setField('utm_content', {{JS - UTM Content}}); } // Populate on page load populate(); // Re-populate before submit (for dynamic forms / SPA frameworks) document.addEventListener('wpcf7beforesubmit', populate); document.addEventListener('submit', populate); })();
Configure GTM Triggers
Correct trigger configuration is critical. Using the wrong trigger can cause UTMs to be missed or form fields to be populated before they exist in the DOM.
| Tag | Trigger | Scope |
|---|---|---|
| UTM Capture Tag | All Pages — Page View | Entire site, all traffic |
| Form Populate Tag | DOM Ready | Form page only (e.g., /contact) |
Testing & Verification
Test 1 — Capture Works
Visit your site with UTM parameters:
https://yoursite.com/contact ?utm_source=google &utm_medium=cpc &utm_campaign=test_campaign &utm_term=crm&utm_content=ad1
Open GTM Preview → verify the UTM Capture Tag fired → check that all 5 GTM variables show correct values.
Test 2 — Overwrite Logic
Without clearing sessionStorage, now visit:
https://yoursite.com/contact ?utm_source=linkedin &utm_medium=paid-social
Expected result — old values must be cleared:
utm_source = 'linkedin' ✓ utm_medium = 'paid-social' ✓ utm_campaign = '' ✓ (cleared) utm_term = '' ✓ (cleared) utm_content = '' ✓ (cleared)
Test 3 — Persistence Across Pages
After landing on a UTM URL, navigate to another page without UTM parameters. Open the browser console and verify:
sessionStorage.getItem('utm_source') // Should still return 'linkedin' sessionStorage.getItem('utm_campaign') // Should still return ''
Test 4 — Form Field Population
Navigate to your form page. Open DevTools and run:
document.querySelector('input[name="utm_source"]').value // Expected: 'linkedin' (or your last UTM source)
Test 5 — End-to-End Salesforce Verification
- Submit a test lead using a URL with known UTM parameters
- Open the Lead record in Salesforce
- Verify all 5 UTM fields are populated with the expected values
- Verify fields that were blank in the URL are genuinely empty (not inherited)
Common Mistakes to Avoid
Only updating the UTMs that are present instead of overwriting the entire set. This is the root cause of attribution contamination.
Using a Page View trigger for the form populate tag. Use DOM Ready to ensure the hidden fields exist before you try to populate them.
Recapturing UTMs only on the form page. Capture must happen on all pages because users often land on a blog post or home page first.
Using display names instead of API names. In Salesforce, field API names are case-sensitive and may include a custom suffix like __c.
Single-page apps or forms loaded via JavaScript may not be in the DOM when your populate tag fires. Add a fallback listener on form submit events.
Advanced Enhancements
Once the basics are working reliably, consider these additional tracking capabilities:
- First-touch vs. last-touch attribution — store both the first UTM set (in a long-lived cookie) and last UTM set (sessionStorage) to understand the full customer journey
- Landing page URL — capture
window.location.hrefon first visit to know exactly which page brought the lead in - Referrer tracking — store
document.referrerto catch organic and direct traffic that doesn’t carry UTM parameters - GCLID tracking — capture Google’s click ID (
gclid) for offline conversion import back into Google Ads - Server-side tracking — use a server-side GTM container or API for more reliable, ad-blocker-resistant attribution
- Multi-touch attribution models — use tools like HubSpot Attribution or a custom data warehouse to model linear, time-decay, or position-based attribution across touchpoints
Summary: The Complete Setup Checklist
- Add hidden UTM fields to your Salesforce form
- Create UTM Capture Tag in GTM — fires on All Pages
- Implement overwrite-all logic (not merge logic)
- Store in sessionStorage (or cookies for long cycles)
- Create 5 GTM Custom JavaScript Variables
- Create Form Populate Tag — fires on DOM Ready, form page only
- Test capture, overwrite logic, persistence, and form fields
- Verify end-to-end in Salesforce with a test lead submission
- Map field API names correctly in Salesforce
UTM tracking isn’t just about capturing parameters — it’s about getting attribution right. The difference between broken and reliable Salesforce data comes down to one rule: always overwrite UTMs as a complete set when a new campaign visit is detected. Do that, and your data becomes something you can actually trust.
Part of the Complete GA4 Audit Guide · Implementation · Configuration · Data Validation · Consent Mode
