UTM Transfer Script
Attribution Continuity
Preserves UTM parameters across all internal navigation by automatically appending campaign data to links, ensuring attribution continuity throughout the user journey and preventing attribution loss.
Key Features
Script Overview
What It Does
This script automatically appends UTM parameters to all links on your website, ensuring campaign attribution is preserved as users navigate through your site and click on external links. Essential for maintaining attribution across multiple touchpoints.
Use Case
Crucial for multi-page funnels and external link tracking without losing campaign data. Perfect for websites with affiliate links, partner integrations, or complex user journeys spanning multiple domains.
Key Benefits
- •Maintains campaign attribution across navigation
- •Preserves UTM data for external link clicks
- •Enables cross-domain attribution tracking
- •Improves conversion path analysis
- •Enhances multi-touch attribution modeling
- •Reduces attribution data loss
JavaScript Code
<script>
(function() {
'use strict';
// UTM Transfer Script
// Version: 1.0
// Description: Transfers UTM parameters to all links for attribution continuity
// Last Updated: 2025-09-09
// Configuration
var config = {
// UTM parameters to transfer
utmParams: ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content'],
// Additional parameters to transfer
customParams: ['gclid', 'fbclid', 'msclkid', 'ref'],
// Link selectors to process
linkSelectors: 'a[href]',
// Domains to treat as internal (will get UTM params)
internalDomains: [], // Leave empty to auto-detect or add specific domains
// External domains to include (normally external links are processed)
includeExternalDomains: [], // Specific external domains to include
// Exclude links with these attributes/classes
excludeSelectors: [
'[data-no-utm]',
'.no-utm',
'[href^="mailto:"]',
'[href^="tel:"]',
'[href^="#"]',
'[href^="javascript:"]'
].join(','),
// Storage key for UTM data
storageKey: 'utm_data',
// Debug mode
debug: false
};
// Utility functions
function debugLog(message, data) {
if (config.debug) {
console.log('[UTM Transfer] ' + message, data || '');
}
}
function getCurrentDomain() {
return window.location.hostname.toLowerCase();
}
function isInternalDomain(hostname) {
var currentDomain = getCurrentDomain();
// If no internal domains specified, treat same domain as internal
if (config.internalDomains.length === 0) {
return hostname === currentDomain;
}
// Check against specified internal domains
return config.internalDomains.some(function(domain) {
return hostname.includes(domain.toLowerCase());
});
}
function shouldIncludeExternalDomain(hostname) {
if (config.includeExternalDomains.length === 0) {
return true; // Include all external domains by default
}
return config.includeExternalDomains.some(function(domain) {
return hostname.includes(domain.toLowerCase());
});
}
function getStoredUTMParams() {
try {
var stored = localStorage.getItem(config.storageKey);
if (stored) {
var data = JSON.parse(stored);
var params = {};
// Extract UTM parameters
config.utmParams.forEach(function(param) {
if (data[param]) {
params[param] = data[param];
}
});
// Extract custom parameters
config.customParams.forEach(function(param) {
if (data[param]) {
params[param] = data[param];
}
});
return params;
}
} catch (e) {
debugLog('Error reading stored UTM params:', e);
}
return {};
}
function getCurrentUTMParams() {
var params = {};
var urlParams = new URLSearchParams(window.location.search);
// Get UTM parameters from current URL
config.utmParams.forEach(function(param) {
var value = urlParams.get(param);
if (value) {
params[param] = value;
}
});
// Get custom parameters from current URL
config.customParams.forEach(function(param) {
var value = urlParams.get(param);
if (value) {
params[param] = value;
}
});
return params;
}
function combineParams(currentParams, storedParams) {
// Current URL params take precedence over stored params
return Object.assign({}, storedParams, currentParams);
}
function appendParamsToUrl(url, params) {
try {
var urlObj = new URL(url, window.location.origin);
var searchParams = urlObj.searchParams;
// Add parameters that don't already exist
Object.keys(params).forEach(function(key) {
if (!searchParams.has(key) && params[key]) {
searchParams.set(key, params[key]);
}
});
return urlObj.toString();
} catch (e) {
debugLog('Error appending params to URL:', e);
return url;
}
}
function shouldProcessLink(link) {
var href = link.getAttribute('href');
// Skip if no href
if (!href) return false;
// Skip excluded selectors
if (config.excludeSelectors && link.matches(config.excludeSelectors)) {
return false;
}
// Skip if already processed
if (link.hasAttribute('data-utm-processed')) {
return false;
}
try {
var linkUrl = new URL(href, window.location.origin);
var linkHostname = linkUrl.hostname.toLowerCase();
var currentDomain = getCurrentDomain();
// Process internal links
if (isInternalDomain(linkHostname)) {
return true;
}
// Process external links if they should be included
if (linkHostname !== currentDomain && shouldIncludeExternalDomain(linkHostname)) {
return true;
}
} catch (e) {
debugLog('Error processing link URL:', e);
}
return false;
}
function processLink(link, params) {
if (Object.keys(params).length === 0) {
return; // No parameters to add
}
var originalHref = link.getAttribute('href');
var newHref = appendParamsToUrl(originalHref, params);
if (newHref !== originalHref) {
link.setAttribute('href', newHref);
link.setAttribute('data-utm-processed', 'true');
debugLog('Processed link:', {
original: originalHref,
updated: newHref,
params: params
});
}
}
function processAllLinks() {
debugLog('Processing all links on page');
// Get current and stored UTM parameters
var currentParams = getCurrentUTMParams();
var storedParams = getStoredUTMParams();
var allParams = combineParams(currentParams, storedParams);
debugLog('UTM parameters to transfer:', allParams);
if (Object.keys(allParams).length === 0) {
debugLog('No UTM parameters found to transfer');
return;
}
// Process all links
var links = document.querySelectorAll(config.linkSelectors);
var processedCount = 0;
Array.prototype.forEach.call(links, function(link) {
if (shouldProcessLink(link)) {
processLink(link, allParams);
processedCount++;
}
});
debugLog('Processed ' + processedCount + ' links with UTM parameters');
}
function observeNewLinks() {
// Create mutation observer to handle dynamically added links
var observer = new MutationObserver(function(mutations) {
var shouldProcess = false;
mutations.forEach(function(mutation) {
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
// Check if any added nodes contain links
Array.prototype.forEach.call(mutation.addedNodes, function(node) {
if (node.nodeType === 1) { // Element node
if (node.tagName === 'A' || node.querySelector && node.querySelector('a')) {
shouldProcess = true;
}
}
});
}
});
if (shouldProcess) {
debugLog('New links detected, processing...');
setTimeout(processAllLinks, 100); // Small delay to ensure DOM is ready
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
debugLog('Link observer initialized');
}
// Main initialization
function initUTMTransfer() {
debugLog('Initializing UTM Transfer Script');
// Process existing links
processAllLinks();
// Observe for new links
observeNewLinks();
// Re-process links when hash changes (for SPAs)
window.addEventListener('hashchange', function() {
setTimeout(processAllLinks, 100);
});
}
// Initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initUTMTransfer);
} else {
initUTMTransfer();
}
// Expose utility functions
window.utmTransfer = {
processLinks: function() {
processAllLinks();
},
getParams: function() {
return combineParams(getCurrentUTMParams(), getStoredUTMParams());
},
refreshProcessing: function() {
// Remove processed flags and reprocess
var processedLinks = document.querySelectorAll('[data-utm-processed]');
Array.prototype.forEach.call(processedLinks, function(link) {
link.removeAttribute('data-utm-processed');
});
processAllLinks();
}
};
})();
</script>
💡 Pro Tip: Copy the entire code block above and paste it directly into a GTM Custom HTML tag. The script is self-contained and ready to use immediately.