Get Marketing Attribution Data in Your CRM with Google Tag Manager
Every marketer wants to know which campaigns drive real leads — not just website traffic, but actual form submissions, demo requests, and sales conversations.
The problem is that your website analytics (Google Analytics, etc.) and your CRM (HubSpot, Salesforce, Pipedrive, etc.) live in separate worlds. Google Analytics knows where a visitor came from. Your CRM knows who they are. But neither knows both.
The fix is surprisingly simple: add hidden fields to your contact form and use Google Tag Manager to fill them with the same attribution data that Google Analytics uses — source, medium, campaign, and more. When the user submits the form, those hidden fields travel with it into your CRM.
It's not the most robust attribution solution — for that you'd want a data warehouse and proper modeling — but it's the fastest, simplest way to attach marketing attribution to individual contacts. And it's often all you need.
Live Demo
Try the form below. Click one of the "Simulate Campaign Traffic" links to reload this page with sample UTM parameters — as if you arrived from a marketing campaign — then watch the hidden attribution fields populate automatically.
What to Track
For each form submission, we capture the following hidden fields. You can choose to track first touch, last touch, or both:
- First touch — the first time this person visited your site. Where did they originally come from?
- Last touch — the most recent visit before they submitted the form. Often the same as first touch, but not always.
For each touch, we capture:
| Field | Description |
|---|---|
| Landing Page | The full URL where they landed, including any UTM parameters |
| Referrer | The HTTP referrer — the page they came from |
| Source | utm_source, or derived from the referrer (e.g. google) |
| Medium | utm_medium, or derived from the referrer (e.g. organic, cpc) |
| Campaign | utm_campaign if present |
| Content | utm_content if present |
| Term | utm_term if present |
| GCLID | The Google Click ID if this was a Google Ads click |
Plus one shared field:
| Field | Description |
|---|---|
| GA Client ID | The Google Analytics client ID, for joining GA and CRM data later |
The GA Client ID is future-proofing. If you ever combine your Google Analytics data with your CRM data (in a data warehouse, for example), having the client ID attached to the email address lets you join them at a very granular level. We wrote about this in more detail in Add Google Analytics Client IDs to Form Submissions.
Why Track Landing Page and Referrer?
You might wonder: why not just capture utm_source, utm_medium, etc.
directly? Why bother with the raw landing page URL and referrer?
Because all attribution is ultimately derived from just two pieces of information: the landing page URL and the referrer. That's the source of truth. Source, medium, campaign, content, term — those are all derived from the landing page and the referrer.
Storing the raw URL is future-proofing. Here's an example: say you're running
Facebook ads but you forgot to add UTM parameters to some of them. If you only
captured utm_source, those submissions would show up as (direct) / (none).
But if you captured the landing page URL, you'd see the fbclid parameter in it
and know the visit came from Facebook — even without UTMs.
Down the road, when you notice something wrong with your attribution model, you can always go back to the landing page URL and re-derive the correct source. You can't go back and capture something you didn't save.
Rule of thumb: always track the most granular piece of information available at the point of collection.
How It Works
The approach has three parts:
- Persist campaign data in cookies — so the landing page URL and referrer survive across page navigations within a session
- Derive marketing fields — parse UTM parameters from the URL, or infer source/medium from the referrer
- Fill hidden form fields — use GTM to write the values into hidden
<input>elements on your form before the user submits
Step 1: Persist Campaign Data
Simo Ahava created a wonderful GTM tag template called
Persist Campaign Data
that does exactly what we need. When a visitor lands on your site with UTM
parameters, the tag saves the full landing page URL in a first-party cookie
(__gtm_campaign_url). It also saves the referrer in a separate cookie
(__gtm_referrer) when the visitor came from a different domain.
You'll want two instances of this tag:
- Persist Campaign Data - Last Touch — fires on All Pages so the cookies update on each visit (captures the most recent visit)
- Persist Campaign Data - First Touch — uses trigger conditions so it only fires when the first-touch cookies don't exist yet (preserves the original visit)
In this template, first-vs-last behavior is driven by trigger logic, not by a separate "cookie type" setting.
Step 2: Derive Marketing Fields
The persisted cookies give us the raw landing page URL and referrer. Now we need to derive the marketing attribution fields — just like Google Analytics does.
Before creating the JavaScript variable below, create these two GTM variables:
GTM Campaign URL→ 1st Party Cookie (__gtm_campaign_url) with URI-decode cookie enabledGTM Referrer→ 1st Party Cookie (__gtm_referrer) with URI-decode cookie enabled
Create a Custom JavaScript variable in GTM called "Marketing Fields - Last":
function() {
var url = {{GTM Campaign URL}};
var referrer = {{GTM Referrer}};
var result = {
source: '', medium: '', campaign: '',
content: '', term: '', gclid: ''
};
// Parse UTM params and gclid from the landing page URL
var params = {};
if (url) {
try {
var parsed = new URL(url);
var sp = parsed.searchParams;
params = {
source: sp.get('utm_source') || '',
medium: sp.get('utm_medium') || '',
campaign: sp.get('utm_campaign') || '',
content: sp.get('utm_content') || '',
term: sp.get('utm_term') || '',
gclid: sp.get('gclid') || ''
};
} catch(e) {}
}
// Always carry these through regardless of attribution path
result.gclid = params.gclid || '';
result.campaign = params.campaign || '';
result.content = params.content || '';
result.term = params.term || '';
// Attribution priority: UTMs > gclid > referrer > direct
if (params.source) {
result.source = params.source;
result.medium = params.medium || '(not set)';
} else if (params.gclid) {
result.source = 'google';
result.medium = 'cpc';
} else if (referrer) {
try {
var refHost = new URL(referrer).hostname.replace(/^www\./, '');
var searchEngines = [
'google', 'bing', 'yahoo', 'duckduckgo',
'baidu', 'yandex', 'ecosia'
];
var matched = false;
for (var i = 0; i < searchEngines.length; i++) {
if (refHost.indexOf(searchEngines[i]) !== -1) {
result.source = searchEngines[i];
result.medium = 'organic';
matched = true;
break;
}
}
if (!matched) {
var socialMap = {
'facebook.com': 'facebook', 'fb.com': 'facebook',
'instagram.com': 'instagram',
'twitter.com': 'twitter', 'x.com': 'twitter', 't.co': 'twitter',
'linkedin.com': 'linkedin',
'pinterest.com': 'pinterest',
'reddit.com': 'reddit',
'tiktok.com': 'tiktok',
'youtube.com': 'youtube'
};
for (var domain in socialMap) {
if (refHost === domain || refHost.indexOf('.' + domain) !== -1) {
result.source = socialMap[domain];
result.medium = 'social';
matched = true;
break;
}
}
}
if (!matched) {
result.source = refHost;
result.medium = 'referral';
}
} catch(e) {
result.source = '(referrer parse error)';
result.medium = 'referral';
}
} else {
result.source = '(direct)';
result.medium = '(none)';
}
return result;
}
This follows the same attribution logic as Google Analytics:
- UTM parameters → use them directly (e.g.
facebook / paid-social) - GCLID without UTMs →
google / cpc - Referrer is a search engine →
google / organic,bing / organic, etc. - Referrer is a social network →
facebook / social,linkedin / social, etc. - Referrer is anything else →
somedomain.com / referral - Nothing →
(direct) / (none)
Create a second variable called "Marketing Fields - First" with the same code, but change the first two lines to read from the first-touch cookies:
var url = {{GTM Campaign URL - First}};
var referrer = {{GTM Referrer - First}};
Step 3: Get the GA Client ID
Create a Custom JavaScript variable called "GA - Client ID":
function() {
var match = document.cookie.match(/_ga=GA\d+\.\d+\.(.+?)(?:;|$)/);
return match ? match[1] : '';
}
This reads the _ga cookie that Google Analytics sets and extracts the client ID
portion. It's synchronous and doesn't depend on GA4 being fully initialized, so
it's reliable in GTM.
Step 4: Fill the Hidden Form Fields
Finally, create a Custom HTML tag that finds the hidden inputs on the page and fills them with the values from the variables above:
<script>
// Landing Page & Referrer
var landingPageFirstSelector = {{Landing Page First Selector}};
var referrerFirstSelector = {{Referrer First Selector}};
var landingPageLastSelector = {{Landing Page Last Selector}};
var referrerLastSelector = {{Referrer Last Selector}};
var landingPageFirst = {{GTM Campaign URL - First}};
var referrerFirst = {{GTM Referrer - First}};
var landingPageLast = {{GTM Campaign URL}};
var referrerLast = {{GTM Referrer}};
// Derived Marketing Fields & GA Client ID
var marketingFirst = {{Marketing Fields - First}};
var marketingLast = {{Marketing Fields - Last}};
var gaClientId = {{GA - Client ID}};
// Helper: fill a field by CSS selector
function fillField(selector, value) {
if (!selector || !value) return;
var el = document.querySelector(selector);
if (el) el.value = value;
}
// Landing page & referrer
fillField(landingPageFirstSelector, landingPageFirst);
fillField(referrerFirstSelector, referrerFirst);
fillField(landingPageLastSelector, landingPageLast);
fillField(referrerLastSelector, referrerLast);
// Marketing fields - First Touch
fillField('input[name="marketing_source_first"]', marketingFirst.source);
fillField('input[name="marketing_medium_first"]', marketingFirst.medium);
fillField('input[name="marketing_campaign_first"]', marketingFirst.campaign);
fillField('input[name="marketing_content_first"]', marketingFirst.content);
fillField('input[name="marketing_term_first"]', marketingFirst.term);
fillField('input[name="gclid_first"]', marketingFirst.gclid);
// Marketing fields - Last Touch
fillField('input[name="marketing_source_last"]', marketingLast.source);
fillField('input[name="marketing_medium_last"]', marketingLast.medium);
fillField('input[name="marketing_campaign_last"]', marketingLast.campaign);
fillField('input[name="marketing_content_last"]', marketingLast.content);
fillField('input[name="marketing_term_last"]', marketingLast.term);
fillField('input[name="gclid_last"]', marketingLast.gclid);
// GA Client ID (same for both touches)
fillField('input[name="ga-client-id"]', gaClientId);
</script>
The selector variables (e.g. {{Landing Page First Selector}}) are Constant
variables in GTM that hold CSS selectors like
input[name="landing-page-first"]. This makes it easy to update the selectors
without editing the script — useful if your form tool names the inputs
differently.
Trigger Configuration
The "Fill Form" tag needs to fire after the Persist Campaign Data tags and after the form is visible on the page. In the GTM recipe below, we use a Trigger Group that waits for:
- Window Loaded — so the page (and form) are fully rendered
- Form Visible — a custom event (or Element Visibility trigger) that confirms the form is in the DOM
This ensures GTM doesn't try to fill inputs that don't exist yet.
Recommended Hidden Field Names
Name your hidden <input> elements using the names below. The GTM recipe at the
bottom of this post is pre-configured to look for these exact names — it'll work
out of the box.
If you can't use these names (because of your form tool's limitations), you can update the CSS selector variables in GTM to match whatever names you use.
First Touch:
name attribute | What it captures |
|---|---|
landing-page-first | Full landing page URL |
referrer-first | HTTP referrer |
marketing_source_first | Source (from UTMs, gclid, or referrer) |
marketing_medium_first | Medium (from UTMs, gclid, or referrer) |
marketing_campaign_first | Campaign (from utm_campaign) |
marketing_content_first | Content (from utm_content) |
marketing_term_first | Term (from utm_term) |
gclid_first | Google Click ID |
Last Touch:
name attribute | What it captures |
|---|---|
landing-page-last | Full landing page URL |
referrer-last | HTTP referrer |
marketing_source_last | Source (from UTMs, gclid, or referrer) |
marketing_medium_last | Medium (from UTMs, gclid, or referrer) |
marketing_campaign_last | Campaign (from utm_campaign) |
marketing_content_last | Content (from utm_content) |
marketing_term_last | Term (from utm_term) |
gclid_last | Google Click ID |
Shared:
name attribute | What it captures |
|---|---|
ga-client-id | Google Analytics Client ID |
In your HTML, these are just standard hidden inputs:
<!-- First Touch -->
<input type="hidden" name="landing-page-first" />
<input type="hidden" name="referrer-first" />
<input type="hidden" name="marketing_source_first" />
<input type="hidden" name="marketing_medium_first" />
<input type="hidden" name="marketing_campaign_first" />
<input type="hidden" name="marketing_content_first" />
<input type="hidden" name="marketing_term_first" />
<input type="hidden" name="gclid_first" />
<!-- Last Touch -->
<input type="hidden" name="landing-page-last" />
<input type="hidden" name="referrer-last" />
<input type="hidden" name="marketing_source_last" />
<input type="hidden" name="marketing_medium_last" />
<input type="hidden" name="marketing_campaign_last" />
<input type="hidden" name="marketing_content_last" />
<input type="hidden" name="marketing_term_last" />
<input type="hidden" name="gclid_last" />
<!-- Shared -->
<input type="hidden" name="ga-client-id" />
Your users will never see these. They're invisible. GTM fills them automatically before the form is submitted.
Connecting to Your CRM
The hidden fields give you the data. But you still need to get that data into your CRM. How you do that depends on your stack:
-
If your form submits directly to your CRM (e.g. HubSpot forms, Salesforce Web-to-Lead) — map the hidden fields to custom properties/fields in the CRM. Most CRM form builders let you add hidden fields natively.
-
If you use a form tool + integration (e.g. Gravity Forms → Zapier → Pipedrive) — make sure Zapier (or whatever integration tool you use) passes the hidden field values through to the CRM. You'll need to create custom fields in the CRM to receive them.
-
If you use a form tool that sends to a webhook — the hidden field values will be in the POST body alongside all the other form data.
The key is: wherever that form gets submitted, you need to set up the pipes to make sure the attribution fields make it all the way to the contact record. For instance, if you're using Pipedrive and Zapier, you'd create custom fields in Pipedrive for each attribution field, then map them in your Zap.
GTM Container Recipe
We've packaged all of the tags, triggers, and variables described above into a GTM container export that you can import directly into your Google Tag Manager account.
Download: gtm-recipe-fill-attribution-data.json
How to import
- Open Google Tag Manager
- Go to Admin → Import Container
- Upload the JSON file
- Choose Merge → Rename conflicting tags, triggers, and variables
- Review the changes and confirm
The recipe includes:
- Persist Campaign Data tags (first touch and last touch) using Simo Ahava's community template
- Marketing Fields Custom JavaScript variables (first and last touch) that derive source, medium, campaign, etc. from the URL and referrer
- GA Client ID variable that reads from the
_gacookie - CSS selector constants for each hidden field
- Fill Form with Campaign Data Custom HTML tag
- Trigger Group that waits for the page to load and the form to be visible
After importing, you'll want to:
- Update the Form Visible trigger to match how your form appears (CSS selector, element ID, etc.)
- Verify the CSS selectors match your hidden field
nameattributes (they will if you used the recommended names above) - Preview and test using GTM's preview mode with sample UTM parameters in the URL
Wrapping Up
This approach gives you a quick, practical way to attach marketing attribution data to every form submission on your site. It's not a replacement for a proper data warehouse with multi-touch attribution modeling — but for most businesses, it's a huge step up from having no attribution data in the CRM at all.
The core idea is simple:
- Persist the landing page URL and referrer in cookies (using Simo Ahava's template)
- Derive source, medium, campaign, etc. from those two fields (using a Custom JavaScript variable)
- Fill hidden form fields with the data (using a Custom HTML tag)
- Pipe the form submission into your CRM with the attribution fields included
If you want to go deeper — building a data warehouse that combines Google Analytics, CRM, and ad platform data for true multi-touch attribution — get in touch. That's what we do.