Cross-Domain tracking with clean urls

Last updated on November 4, 2018

I’ve been told by a lot of clients that the way that Google Analytics cross-domain tracking works is “ugly”, referring to having the linker param attached to the URL.

I must admit is not elegant having all that long hash on the url, thougt it won’t affect the page functionality. In the other side there isn’t any other to pass the current client Id from the Universal Analytics cookie to the destination domain without dealing with server-side hacks (we can’t not read POST data in JS ,yet).

Browsers have the History API . Which holds the current user navigation history,allows us to manipulate it and is widely supported by browsers:

history api support by browser

If you ever dealed with an Ajax based website, I’m sure you have noticied that even if the page does not reload, the url gets changed.

The history API does allow us to play with the current user session history, for example:


The above line will return the number of elements in the session history, if you have browse 4 pages in the current it’ll return 4.


Will return the user back to the previous page in the session.

But we’re going to focus on the pushState and replaceState methods. Those ones will allow us to add a new entry to the history record and will allow us to change the current page pathname without needing to reload the page.

I bet you’re guessing that we’re going to strip out the _ga parameter with those functions and you’re right. This won’t be harmful for the crossdomain tracking since we’re going to do it after the Google Analytics object has been created so it won’t affect our implementation but we’ll end showing the user a cleaned up URL after Google Analytics does all it’s cross-domain tracking magic.

We’ll using the “replaceState” in this example, to avoid users clicking on back button to be sent to the same. This method will just change the URL but WON’T add a new entry to the session history.

To achive this hack, we’ll be using the hitCallback for our Pageview Tag on Google Tag Manager.

In first place, we are going to need a variable that is going to take care of reading the current URL, cleaning it up, and manipulating the browsers URL using the History API.

I’m calling it “remove _ga from url pushState” , feel free to name it at your convenience:

  return function(){
      if ([^&]*)/)) {
          var new_url;
          var rebuilt_querystring;
          // A small function to check if an object is empty
          var isEmptyObject = function(obj) {
              var name;
              for (name in obj) {
                  return false;
              return true;
          // Let's build an object with a key-value pairs from the current URL
          var qsobject =^\?)/, '').split("&").map(function(n) {
              return n = n.split("="),
              this[n[0]] = n[1],
          // Remove the _ga parameter
          delete qsobject['_ga'];
          // Let's rebuilt the querysting from the previous object with the _ga parameter removed
          var rebuilt_querystring = Object.keys(qsobject).map(function(k) {
              if (!qsobject[k]) {
                  return encodeURIComponent(k);
              } else {
                  return encodeURIComponent(k) + '=' + (encodeURIComponent(qsobject[k] || ""));
          // We want to if the current querystring was null
          if (isEmptyObject(qsobject)) {
              new_url = location.pathname + location.hash;
          } else {
              new_url = location.pathname + '?' + rebuilt_querystring + location.hash;
          // Use replace State to update the current page URL
          window.history.replaceState({}, document.title, new_url);

Now we only need to add this new variable as the hitCallBack value for our pageview tag:

So this is what is going to happen now:

1. Google Analytics Object will be created
2. It will process the linker parameter, overriding the current landing domain clientId value as long as the linkerParam value is legit
3. After that the current page URL will be changed for the same URL but with the _ga parameters stripped out.


    • Flat G
      January 24, 2018


      var Url = new Uri();

      if (Url.hasQuery(‘_ga’)) {
      History.replaceState([], document.title, Url.removeQuery(‘_ga’).toString());

  1. Ximo
    March 22, 2018

    It works perfectly. Thanks very much!
    Just wondering if this hitCallback function might have any negative side effect?

    • bhills
      July 23, 2018


      What does your tag and variable configuration look like? I can’t seem to get this to work after following his instructions.

  2. Amol
    May 18, 2018

    It worked perfectly for me. Thanks!
    Any drawbacks to this implementation?

  3. Joachim Tranberg
    September 4, 2018

    This works great. However we have 1 domain with a SPA (angularjs) where the URL path is:

    It flicker, but it comes back to the URL. Anyone with JS knowledge, who can fix this?

  4. Shilpa
    March 28, 2019

    Does it have any negative impact on Site load time?

  5. Amber Alter
    August 8, 2019

    Hi, would adding cross domain tracking and this code to clean up the URL affect bounce rate? After adding the code in this tutorial, my site’s bounce rate dramatically plummeted (went down from 44% to 1%). Was wondering if this happened to anyone else.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.