Cross-domain measurement

This guide describes how to measure events across multiple domains using analytics.js.

Overview

The analytics.js library uses a unique client ID to determine whether a user is new or returning. A user is considered returning if a hit with a matching client ID has already been sent to the same property.

By default, the client ID is stored in the browser's cookies, which means it can only be accessed by pages on the same domain. To track the same Client ID for a given user across different domains, use cross domain tracking.

To share a client ID across domains, you can append it as a query parameter to URLs that point from the current domain (the source domain) to the destination domain that you want to measure. When a user clicks on a link or submits a form on the source domain and navigates to the destination domain, code on the destination page can access the client ID by reading it from the URL.

Getting the client ID on the source domain

To retrieve a client ID on the source domain, you use the get method:

ga(function(tracker) {
  var clientId = tracker.get('clientId');
});

Once you have the client ID on the source domain, you can add it to links that point to the destination domain.

<a href="https://destination.com/?clientId=XXXXXX">destination.com</a>

Setting the client ID on the destination domain

You can tell a tracker object on the destination domain what client ID to use by specifying the client ID field in the create command:

ga('create', 'UA-XXXXX-Y', 'auto', {
  'clientId': getClientIdFromUrl()
});

If a client ID already exists on the destination domain, this method will overwrite it.

Detecting URL sharing

A potential problem with passing the client ID in a URL is that users share URLs and it's possible a user will share a URL that contains a client ID belonging to someone else.

One way to avoid this problem is to append a timestamp to the client ID. This allows you to detect when the URL was originally created, and if too much time has passed, consider the client ID invalid. In addition to a timestamp, you can append the user agent string or other browser or device-specific metadata. Then on the destination domain, if the metadata does not match, you'll know the client ID originated from someone else.

Ignoring self-referrals

A new referral campaign will be created whenever the document referrer of a page comes from a hostname that does not match any of the entries in the Referral Exclusion list for your property.

By default, the Referral Exclusion list includes only the domain that you provided when the property was first created. To prevent new referral campaigns from being generated when users navigate across domains, you must add an entry for each domain you wish to measure in the Referral Exclusion list.

Iframes

The technique described above requires JavaScript code that runs after analytics.js loads. Since <iframe> elements typically exist on the page prior to analytics.js being loaded, appending the client ID to the URL in the iframe's source parameter is often not an option.

To solve this problem you can configure the page inside the iframe to delay creating its tracker until after it receives the client ID data from the parent page. And on the parent page you configure it to send the client ID to the iframe page using postMessage.

Here's an example of the parent page code on source.com:

<iframe id="destination-frame" src="https://destination.com"></iframe>

<script>
ga('create', 'UA-XXXXX-Y', 'auto');
ga(function(tracker) {
  // Gets the client ID of the default tracker.
  var clientId = tracker.get('clientId');

  // Gets a reference to the window object of the destionation iframe.
  var frameWindow = document.getElementById('destination-frame').contentWindow;

  // Sends the client ID to the window inside the destination frame.
  frameWindow.postMessage(clientId, 'https://destination.com');
});
</script>

And here's the code that would receive the message in the iframe hosted on destination.com:

window.addEventListener('message', function(event) {
  // Ignores messages from untrusted domains.
  if (event.origin != 'https://destination.com') return;

  ga('create', 'UA-XXXXX-Y', 'auto', {
    clientId: event.data
  });
});

It's possible that analytics.js will fail to load on the parent page and then the page in the iframe will never receive the client ID. How you handle this case depends on how important it is that the client IDs match.

If you only want to capture data when you know the client IDs are the same, the above code is sufficient. If you want to capture data on the page in the frame regardless of whether it receives the client ID from the parent page, you'll have to add a fallback.

The following code uses a timeout on the page in the iframe to handle the case where the parent page is slow or fails to send the client ID:

// Stores whether or not the tracker has been created.
var trackerCreated = false;

function createTracker(opt_clientId) {
  if (!trackerCreated) {
    var fields = {};
    if (opt_clientId) {
      fields.clientId = opt_clientId;
    }

    ga('create', 'UA-XXXXX-Y', 'auto', fields);
    trackerCreated = true;
  }
}


window.addEventListener('message', function(event) {
  // Ignores messages from untrusted domains.
  if (event.origin != 'https://destination.com') return;

  // Creates the tracker with the data from the parent page.
  createTracker(event.data);
});


// Waits for three seconds to receive the client ID from the parent page.
// If that doesn't happen, it creates the tracker as normal.
setTimeout(createTracker, 3000);