By default, UTM parameters disappear as soon as a user clicks to another page on your website. This breaks your tracking and leaves you guessing where leads are coming from. To fix that, we’re going to repopulate the UTM parameters by automatically appending them to your internal links. This way, the UTM data follows the user as they navigate your site, helping keep your attribution accurate without relying on cookies.

Bonus Tip

You can capture this UTM data in your form fill so you can visibly see the UTM parameters for each lead or sale.

Important Note

This guide walks through how to install this via Google Tag Manager (GTM); however, you can use the code within this article to accomplish the same thing by installing it into your <head> code sitewide.

How to Implement

  1. Open Google Tag Manager account

  2. Create New Tag

  3. Click Tag Configuration

  4. Select Custom Code

  5. Copy and paste the code snippet below:
    Note: I didn’t write this code originally. I’ve used it for years and don’t know the exact source, but I’ve run it through ChatGPT and can confirm it’s safe and works as intended. If you know the original author, feel free to email me so I can give proper credit.

  6. In the code (line 4) replace ENTER-WEBSITE-URL.COM with your domain (for example: dougsirkoch.com)

  7. Set Trigger to All Pages

  8. Save

  9. Publish changes

  10. Test and make sure the URL Parameters follow you through your website.

<script>
(function() {
  var domainsToDecorate = [
          'ENTER-WEBSITE-URL.COM', //add or remove domains (without https or trailing slash)
      ],
      queryParams = [
          'utm_source', //add or remove query parameters you want to transfer
          'utm_medium',
          'utm_campaign',
          'utm_term',
      ]
  // 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('&');
  }

  // borrowed from https://stackoverflow.com/questions/831030/
  // a function that retrieves the value of a query parameter
  function getQueryParam(name) {
      if (name = (new RegExp('[?&]' + encodeURIComponent(name) + '=([^&]*)')).exec(window.location.search))
          return decodeURIComponent(name[1]);
  }

})();
</script>

Testing Instructions

  1. Paste your website into the Website URL field

  2. Enter in the parameters you intend to capture

    1. campaign source = campaign-test

    2. campaign medium = medium-test

    3. campaign name = name-test

    4. campaign term = term-test

  3. Copy your URL from the Share the Generated Campaign URL box and paste it into a new window

  4. Navigate around the website and ensure UTM parameter persist

By setting up persistent UTM parameters, you'll get a much clearer view of where your leads and sales are actually coming from. No more guessing. No more broken tracking. Whether you're running ads, email campaigns, or driving traffic from social, this setup helps keep your attribution accurate across every page of your site. It's a small step that makes a big difference.

Keep Reading