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
<script>
(function() {
// Helper: parse query string into object
function getQueryParams() {
const params = {};
const query = window.location.search.substring(1).split("&");
query.forEach(pair => {
if (pair) {
const [key, value] = pair.split("=");
params[decodeURIComponent(key)] = decodeURIComponent(value || "");
}
});
return params;
}
// Save UTMs/click IDs from URL into sessionStorage
const params = getQueryParams();
const keysOfInterest = ["utm_source","utm_medium","utm_campaign","utm_term","utm_content","gclid","fbclid"];
keysOfInterest.forEach(key => {
if (params[key]) {
sessionStorage.setItem(key, params[key]);
}
});
// Retrieve stored values
const storedParams = {};
keysOfInterest.forEach(key => {
const val = sessionStorage.getItem(key);
if (val) storedParams[key] = val;
});
// Update all internal links with stored params
if (Object.keys(storedParams).length > 0) {
const paramString = new URLSearchParams(storedParams).toString();
document.querySelectorAll("a[href]").forEach(link => {
try {
const url = new URL(link.href, window.location.origin);
// Only modify internal links
if (url.origin === window.location.origin) {
keysOfInterest.forEach(k => url.searchParams.set(k, storedParams[k] || ""));
link.href = url.toString();
}
} catch (e) {
// Ignore invalid URLs (like mailto:)
}
});
}
// Populate hidden form fields with stored params
document.addEventListener("DOMContentLoaded", () => {
keysOfInterest.forEach(key => {
const val = storedParams[key];
if (val) {
const field = document.querySelector(`input[name="${key}"]`);
if (field) field.value = val;
}
});
});
})();
</script>
What This Script Does
Captures UTM parameters and click IDs from the first page a visitor lands on.
Stores them in sessionStorage so they’re available as the visitor navigates.
Appends those values to internal links automatically, ensuring they follow the user through the site.
Populates any hidden form fields that match the parameter names (utm_source, gclid, etc.) so attribution data flows directly into your CRM.
Notes & Tips
Works with most form builders as long as you can add hidden fields.
If you’re running AJAX or multi-step forms, you may need to re-run the injection once the form is fully loaded.
Because this script checks against the current domain dynamically, there’s no need to hard-code your domain name.



















