Persisting URL Parameters Across Pages

When a visitor lands on your website from a paid ad or tracked link, their URL may contain important tracking data like UTM parameters or click IDs (e.g., ?utm_source=google&utm_medium=cpc&gclid=123abc). These values tell you how that visitor arrived—but if the user clicks through to another page before submitting your form, the parameters are lost.
The Problem
Many users don’t submit a form on the same page they land on. If the attribution data is only present in the initial URL and isn’t stored or carried forward, it won’t be available to populate the hidden fields in your form later.
This means your CRM won’t know how the lead found you—making campaign performance harder to track.
The Solution
To preserve attribution data across multiple pages, we use a JavaScript snippet that:
Captures URL parameters on the initial page visit
Automatically appends them to internal links
Ensures the parameters follow the visitor throughout their session
This ensures attribution fields are still available when the form is eventually submitted—whether on page 1 or page 10 of their visiting session.
Implementation with Google Tag Manager:
We’ll solve this using a single JavaScript tag in Google Tag Manager (GTM).
Steps:
Go to your Google Tag Manager workspace.
Create a new tag:
Tag Type: Custom HTML
Trigger: All Pages
Paste the following code into the tag editor
Replace YOUR-DOMAIN.com with your actual domain name in the code below.
<script>
(function() {
var domainsToDecorate = [
],
queryParams = [
'utm_medium',
'gclid',
'utm_source',
'utm_campaign',
'utm_keyword',
'ref'
]
// do not edit anything below this line
var links = document.querySelectorAll('a');
// check if links contain domain from the domainsToDecorate array and then decorates
for (var linkIndex = 0; linkIndex < links.length; linkIndex++) {
for (var domainIndex = 0; domainIndex < domainsToDecorate.length; domainIndex++) {
if (links[linkIndex].href.indexOf(domainsToDecorate[domainIndex]) > -1 && links[linkIndex].href.indexOf("#") === -1) {
links[linkIndex].href = decorateUrl(links[linkIndex].href);
}
}
}
// decorates the URL with query params
function decorateUrl(urlToDecorate) {
urlToDecorate = (urlToDecorate.indexOf('?') === -1) ? urlToDecorate + '?' : urlToDecorate + '&';
var collectedQueryParams = [];
for (var queryIndex = 0; queryIndex < queryParams.length; queryIndex++) {
if (getQueryParam(queryParams[queryIndex])) {
collectedQueryParams.push(queryParams[queryIndex] + '=' + getQueryParam(queryParams[queryIndex]))
}
}
return urlToDecorate + collectedQueryParams.join('&');
}
function getQueryParam(name) {
if (name = (new RegExp('[?&]' + encodeURIComponent(name) + '=([^&]*)')).exec(window.location.search))
return decodeURIComponent(name[1]);
}
})();
</script>
What This Script Does:
Detects all internal links pointing to your domain
Appends any available UTM parameters and click IDs from the current URL to those links
Ensures attribution data “travels” with the user as they browse your site
With this in place, we can now reliably capture attribution fields later—on any form, on any page.
Populating Hidden Fields with JavaScript:
Now that we’re preserving URL parameters across internal links, the next step is to insert those values into the hidden form fields before the form is submitted. This allows attribution data to be passed into your CRM along with the rest of the form data.
Because websites use different platforms and form builders (Gravity Forms, Elementor, HubSpot, native HTML, etc.), we’ll use a simple JavaScript approach that works across nearly all environments—as long as your hidden fields are properly named.
Basic Requirements
To populate hidden form fields with attribution data, your form must:
Include the correct hidden fields (e.g., utm_source, gclid, etc.)
Load after the page has access to the JavaScript snippet below
Use the exact field name attribute that matches the parameter
Universal JavaScript Snippet
Add this script to your website site-wide (in your page footer or in Google Tag Manager as a “Custom HTML” tag set to fire on All Pages):
<script>
(function () {
var search = window.location.search;
if (!search) return;
// Parse query params (ES5)
var params = {};
var pairs = search.replace(/^\?/, '').split('&');
for (var i = 0; i < pairs.length; i++) {
if (!pairs[i]) continue;
var kv = pairs[i].split('=');
var k = decodeURIComponent(kv[0] || '');
var v = decodeURIComponent((kv[1] || '').replace(/\+/g, ' '));
if (k) params[k] = v;
}
var keys = ['utm_medium','gclid','fbclid','utm_source','utm_campaign','utm_keyword'];
// If there may be multiple inputs per name, set them all
for (var j = 0; j < keys.length; j++) {
var name = keys[j];
var val = params[name];
if (!val) continue;
var nodeList = document.querySelectorAll('input[name="' + name + '"]');
for (var n = 0; n < nodeList.length; n++) {
nodeList[n].value = val;
}
}
})();
</script>
What This Script Does:
Looks at the current page URL
Finds any <input> elements with a matching name attribute
Sets the value of those fields to the matching parameter in the URL
Notes & Tips:
Works with most form builders that allow you to create custom hidden fields
If you’re using multiple forms on one page, make sure each one uses unique IDs or test that all hidden fields are being populated
For more complex setups (multi-step forms, AJAX-loaded forms), you may need to delay this script slightly or re-trigger it once the form loads