Home
Dynamic UTM Parameter Capture
🎯

Dynamic UTM Parameter Capture

Campaign Attribution & Tracking

Automatically captures UTM parameters from the URL, stores them in localStorage for session persistence, and pushes attribution data to Google Tag Manager's dataLayer for comprehensive campaign tracking.

AttributionBeginnerAttribution & Tracking

Key Features

Automatic UTM parameter detection
localStorage persistence across sessions
DataLayer integration for GTM
Custom parameter support
Session-based attribution tracking
Fallback handling for missing parameters

Script Overview

📖

What It Does

This script automatically captures UTM parameters (utm_source, utm_medium, utm_campaign, utm_term, utm_content) from the URL when a user first visits your site, stores them in localStorage for persistence across sessions, and pushes the data to Google Tag Manager's dataLayer for comprehensive campaign attribution.

🎯

Use Case

Perfect for B2B websites that need persistent campaign attribution across sessions. Essential for understanding which marketing campaigns drive the most valuable visitors and conversions, especially for longer sales cycles where users visit multiple times before converting.

Key Benefits

  • Persistent campaign attribution across sessions
  • Better understanding of marketing performance
  • Enhanced analytics data quality
  • Improved conversion tracking accuracy
  • First-touch attribution modeling
  • Cross-session user journey tracking

JavaScript Code

<script>
(function() {
  'use strict';
  
  // Dynamic UTM Parameter Capture Script
  // Version: 1.2
  // Description: Captures UTM parameters and stores them for attribution
  // Last Updated: 2025-09-09
  
  // Configuration
  var config = {
    // UTM Parameters to capture
    paramNames: ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content'],
    
    // Additional custom parameters (add more as needed)
    customParams: ['gclid', 'fbclid', 'msclkid', 'ref', 'referrer'],
    
    // LocalStorage key for storing UTM data
    storageKey: 'utm_data',
    
    // DataLayer event name
    dataLayerEvent: 'utm_captured',
    
    // Expiration time in days (30 days default)
    expirationDays: 30,
    
    // Override existing UTM data (false = keep first-touch)
    overrideExisting: false,
    
    // Debug mode (set to true for console logging)
    debug: false
  };

  // Utility functions
  function debugLog(message, data) {
    if (config.debug) {
      console.log('[UTM Capture] ' + message, data || '');
    }
  }

  function getUrlParameters() {
    var params = {};
    var urlParams = new URLSearchParams(window.location.search);
    
    // Capture UTM parameters
    config.paramNames.forEach(function(param) {
      var value = urlParams.get(param);
      if (value) {
        params[param] = value;
      }
    });
    
    // Capture custom parameters
    config.customParams.forEach(function(param) {
      var value = urlParams.get(param);
      if (value) {
        params[param] = value;
      }
    });
    
    return params;
  }

  function getStoredUtmData() {
    try {
      var stored = localStorage.getItem(config.storageKey);
      if (stored) {
        var data = JSON.parse(stored);
        
        // Check if data has expired
        if (data.timestamp && data.expirationDays) {
          var expirationTime = data.timestamp + (data.expirationDays * 24 * 60 * 60 * 1000);
          if (Date.now() > expirationTime) {
            debugLog('UTM data expired, clearing storage');
            localStorage.removeItem(config.storageKey);
            return null;
          }
        }
        
        return data;
      }
    } catch (e) {
      debugLog('Error reading stored UTM data:', e);
    }
    return null;
  }

  function storeUtmData(params) {
    try {
      var dataToStore = {
        ...params,
        timestamp: Date.now(),
        expirationDays: config.expirationDays,
        pageUrl: window.location.href,
        referrer: document.referrer || 'direct'
      };
      
      localStorage.setItem(config.storageKey, JSON.stringify(dataToStore));
      debugLog('UTM data stored:', dataToStore);
      return dataToStore;
    } catch (e) {
      debugLog('Error storing UTM data:', e);
      return null;
    }
  }

  function pushToDataLayer(utmData) {
    // Ensure dataLayer exists
    window.dataLayer = window.dataLayer || [];
    
    // Prepare event data
    var eventData = {
      event: config.dataLayerEvent,
      utm_data: utmData,
      utm_source: utmData.utm_source || '',
      utm_medium: utmData.utm_medium || '',
      utm_campaign: utmData.utm_campaign || '',
      utm_term: utmData.utm_term || '',
      utm_content: utmData.utm_content || '',
      first_touch_timestamp: utmData.timestamp,
      attribution_type: utmData.isNewCapture ? 'first_touch' : 'existing'
    };

    // Add custom parameters to event data
    config.customParams.forEach(function(param) {
      if (utmData[param]) {
        eventData[param] = utmData[param];
      }
    });

    // Push to dataLayer
    window.dataLayer.push(eventData);
    debugLog('Data pushed to dataLayer:', eventData);
  }

  // Main execution
  function initUTMCapture() {
    debugLog('Initializing UTM capture');
    
    // Get current URL parameters
    var currentParams = getUrlParameters();
    var hasNewParams = Object.keys(currentParams).length > 0;
    
    // Get stored UTM data
    var storedData = getStoredUtmData();
    
    debugLog('Current URL params:', currentParams);
    debugLog('Stored UTM data:', storedData);
    
    var finalUtmData = null;
    
    if (hasNewParams) {
      // New UTM parameters found in URL
      if (!storedData || config.overrideExisting) {
        // Store new parameters (first-touch or override mode)
        finalUtmData = storeUtmData(currentParams);
        if (finalUtmData) {
          finalUtmData.isNewCapture = true;
          debugLog('New UTM parameters captured and stored');
        }
      } else {
        // Keep existing stored data (first-touch attribution)
        finalUtmData = storedData;
        finalUtmData.isNewCapture = false;
        debugLog('Using existing stored UTM data (first-touch)');
      }
    } else if (storedData) {
      // No new parameters, but we have stored data
      finalUtmData = storedData;
      finalUtmData.isNewCapture = false;
      debugLog('Using existing stored UTM data');
    }
    
    // Push to dataLayer if we have UTM data
    if (finalUtmData) {
      pushToDataLayer(finalUtmData);
    } else {
      debugLog('No UTM parameters to process');
      
      // Still push an event for direct traffic
      window.dataLayer = window.dataLayer || [];
      window.dataLayer.push({
        event: config.dataLayerEvent,
        utm_source: 'direct',
        utm_medium: 'none',
        attribution_type: 'direct_traffic'
      });
    }
  }

  // Initialize when DOM is ready
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', initUTMCapture);
  } else {
    // DOM is already ready
    initUTMCapture();
  }

  // Optional: Expose utility functions for advanced users
  window.utmCapture = {
    getStoredData: function() {
      return getStoredUtmData();
    },
    clearStoredData: function() {
      localStorage.removeItem(config.storageKey);
      debugLog('UTM data cleared from storage');
    },
    refreshCapture: function() {
      initUTMCapture();
    }
  };

})();
</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.