Linker (Cross-domain Tracking)

This guide describes how to track users across domains (cross domain tracking) using analytics.js.


The analytics.js library uses a single first-party cookie to store the Client ID that can only be accessed by the domain on which it is set. If you own multiple domains and would like to track users across them, additional work must be done so that the analytics.js cookie data is maintained across each of those domains.

Lets assume you have 2 root domains, the current source domain: source.com and the target destination domain: destination.com. A common strategy to maintain the cookie data across both domains is as follows:

  1. On the source domain, retrieve the cookie data and append the data as a query parameter to all links pointing to the destination domain.
  2. When a user clicks a link from the source to the destination domain, the query parameter, including the cookie data, will be in the final URL of the destination domain.
  3. On the destination domain, if the query parameter is in the URL and valid, extract the cookie data from the URL and write it to a new cookie on the destination domain.

To simplify these steps, analytics.js provides a bunch of helper functions to get cookie data, append to the URL, and retrieve the cookie data on destination domains.

Ignoring Self Referrals

In Universal Analytics, 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 web property.

By default, the Referral Exclusion list includes only the domain that you provided when the web 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 track across in the Referral Exclusion list. For example, if your default URL was configured as source.com, and you need to track a user across both source.com and destination.com, you will need to add destination.com to the referral exclusion list.

For information about referral exclusion lists and where to do this in the administration interface, read Referral Exclusions in the Help Center.

Retrieving / Setting Cookie Data

The analytics.js library uses a single cookie, named _ga, to store an anonymous client ID.

On the source domain, you can retrieve the stored client ID, using the following command:

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

Once run, the value of clientId will look similar to:


This value can then be appended as a query parameter to a URL pointing to a destination domain. On the destination domain, the clientId value can be written to the cookie when the tracker is created:

ga('create', 'UA-XXXXX-Y', 'auto', {
  'clientId': clientId        // Value is retrieved from method above.

This solution is very flexible as it allows developers to pass cookie data between domains, the client and the server, and even between online and offline environments. One downside is that this solution requires significant JavaScript to read and write the client ID to / from the URL. Another issue arises when users share URLs that contain client IDs.

Linker Parameters

One challenge with putting the clientId directly in URLs is if a user bookmarks and then shares that link. If many users use a link with the same client ID, all of those users will appear as the same user in Google Analytics.

To solve the problem, the analytics.js library provides a command to retrieve a special linker parameter that includes both the client ID as well as a hashed timestamp. You get the special linker parameter using the following command:

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

Once run, the value of linkerParam will look similar to:


This query parameter can then be added to all the URLs that point to the destination domain. To write this to the cookie on the destination domain, you must update all the create commands on the destination domain by setting the allowLinker tracking configuration parameter to true:

ga('create', 'UA-XXXXX-Y', 'auto', {'allowLinker': true});

When the tracker is created with allowLinker set to true, analytics.js will check to see if the linker parameter exists in the URL and that the timestamp is no less than 2 minutes old. If it is less than 2 minutes, analytics.js will extract the client ID value from the linker parameter and set the value into the cookie. If the timestamp is older than 2 minutes, then the linker parameter will be ignored.

Decorate Utility Method

To simplify appending linker parameters to URLs, analytics.js provides the decorate utility method. This method accepts a URL and an optional boolean and returns the same URL with the linker parameter appended either in the query or hash portion of the URL.

To retrieve the linker parameter in the query portion of the URL, use:

ga(function(tracker) {
  var linker = new window.gaplugins.Linker(tracker);
  var output = linker.decorate('//destination.com');

This code will execute once the analytics.js library loads. It will create a new Linker object and use the decorate method to get an updated URL. Once complete, the value of output will look like:


linker:decorate also accepts <a>, <area> and <form> Elements. Please see Decorating HTML Links and Decorating HTML Forms

Decorate the Hash Portion of the URL

You can also append the linker parameter to the hash portion of the URL by passing true as a second parameter to the decorate method:

ga(function(tracker) {
  var linker = new window.gaplugins.Linker(tracker);
  var output = linker.decorate('//destination.com', true);

The value of output will now look like:


Since linker parameters are only valid for 2 minutes, if you decorate all the links when the page loads, and a user is on the page for more than 2 minutes, all of the links will have invalid linker parameters.

To solve this, we recommend decorating links in the mousedown and keydown event handlers so that links gets decorated when the either the mouse is depressed or the link is navigated to via the keyboard. This ensures that:

  • The linker parameters do not timeout
  • Functionality works using the different mouse buttons
  • Functionality works when the URL is opened in different tabs / windows
  • Target attributes on HTML links are preserved

To use the decorate method on the following link:

<a id="myLink" href="//destination.com">Go to another domain</a>

You would call the following javascript after the link is added to the page:

var linker;

var myLink = document.getElementById('myLink');             // Add event listeners to link.
addListener(myLink, 'mousedown', decorateMe);
addListener(myLink, 'keydown', decorateMe);

function decorateMe(event) {
  event = event || window.event;                            // Cross browser hoops.
  var target = event.target || event.srcElement;

  if (target && target.href) {                              // Ensure this is a link.
    ga('linker:decorate', target);

// Cross browser way to listen for events.
function addListener(element, type, callback) {
  if (element.addEventListener) element.addEventListener(type, callback);
  else if (element.attachEvent) element.attachEvent('on' + type, callback);

In this example, a cross browser event handler is added to listen for both the mousedown and keydown to the link. When either event occurs, the decorateMe function will be called.

When the decorateMe function is called, the link that generated the event is retrieved. Next a function is called with the ga object so that the function logic is executed only after the analytics.js library has loaded.

Inside the function, a new linker object is created by passing the default tracker to the gaplugins.Linker method. to retrieve the default tracker object. Finally, the linker.decorate method is used to decorate the URL of the link.

When the user releases the mouse button, the link will have been updated with linker parameters, and the default browser behavior will continue.

Decorating HTML Forms

Similar to decorating HTML links, you can use the decorate method to add cross domain linking parameter to forms. Say you have the following form on your site:

<form name="myForm" id="myForm" method="post" action="http://example2.com/mylink2.html">
  How do you feel today? <input type="text" name="emotions"><br>
  <input type="submit">

You can add the following JavaScript code after the form loads to update the action property of the form when it is submitted:

var myForm = document.getElementById('myForm');
addListener(myForm, 'submit', decorateForm);

function decorateForm(event) {
  event = event || window.event;                             // Cross browser hoops.
  var target = event.target || event.srcElement;

  if (target && target.action) {
    ga('linker:decorate', target);

// Cross browser way to listen for events.
function addListener(element, type, callback) {
  if (element.addEventListener) element.addEventListener(type, callback);
  else if (element.attachEvent) element.attachEvent('on' + type, callback);

Now when the form is submitted, the URL of the form will be updated with the linker parameters.

Tracking Across iFrames

Tracking Cross Domain iFrames using linker

The analytics.js library and the decorate method make it easy to track users across iFrames. The process for tracking user across iFrames is very similar to tracking users across domains. The steps you need to go through are:

  • Retrieve the cookie / linker parameter when the iFrame is created
  • Append the parameter to the source, src, of the iFrame
  • On the destination page being loaded in the iFrame, set the allowLinker parameter to true.

The following sections describe what code you need to add on both Source and Destination pages.

Source page

On the page that loads the iFrame, the cookie / linker parameter needs to be added to the iFrame source parameter. This needs to happen when the iFrame is first created so that the iFrame is not loaded multiple times.

To solve this, create a <div> element to hold your iFrame. Once analytics.js loads, dynamically create an iFrame element and use the Linker decorate method to update the iFrame source attribute with the linker parameters. Once updated, append the iFrame inside of the <div> element. The following code demonstrates how to do this:

<div id="myiFrame"></div>

var linker;

function addiFrame(divId, url, opt_hash) {
  return function(tracker) {
    window.linker = window.linker || new window.gaplugins.Linker(tracker);
    var iFrame = document.createElement('iFrame');
    iFrame.src = window.linker.decorate(url, opt_hash);

// Dynamically add the iFrame to the page with proper linker parameters.
ga(addiFrame('myiFrame', 'destination.html'));


In this example, the function addiFrame is used to dynamically add an iFrame to the page. This function accepts the ID of an element in which the iFrame should be appended to, as well as the URL of the iFrame and whether to use the optional hash parameter.

The addiFrame function returns a function that can be passed to the global ga function to ensure the logic is executed only once the analytics.js library has loaded.

Inside the function, a new Linker and iFrame object are created. The linker.decorate method is used to update the iFrame url with the proper linker parameters. Finally, the decorated iFrame is appended to the <div> specified by the divId parameter.

Destination Page

On the destination page loaded in the iFrame, the analytics.js create method in the default tracking snippet needs to be updated to read the linker parameters.

ga('create', 'UA-XXXXX-Y', 'auto', {
  'allowLinker': true

In this example, the allowLinker configuration parameter must be set to true, so that the tracker object will check the URL for linker parameters.

Tracking Cross Domain iFrames using postMessage

This example uses window.postMessage for communication between cross domain iframes. This method is advantageous because it does not block the execution of the iframe.

Source page

// The normal snippet.
ga('create', 'UA-XXXXX-Y', 'auto');
ga('send', 'pageview');

// Replace with your domain here.
var allowedOrigins = ['https://example.com', 'http://example.com'];
function xDomainHandler(event) {
  event = event || window.event;
  var origin = event.origin;

  // Check for the whitelist.
  var found = false;
  for (var i = 0; i < allowedOrigins.length; i++) {
    if (allowedOrigins[i] == origin) {
      found = true;
  if (!found) return;

  // Might be a different message.
  if (event.data != 'send_client_id') return;
  // Get the clientId and send the message.  This might be async.
  ga(function(tracker) {
    var data = {cid: tracker.get('clientId')};
    event.source.postMessage(JSON.stringify(data), origin);
if (window.addEventListener) {
  window.addEventListener('message', xDomainHandler, false);
} else if (window.attachEvent) {
  window.attachEvent('onmessage', xDomainHandler);

On the iframe page

var topOrigin = 'http://example.com'; // Replace with actual origin.
function xDomainHandler(event) {
  event = event || window.event;
  var origin = event.origin;
  if (topOrigin != '*' && topOrigin != event.origin) {
  try {
    var data = JSON.parse(event.data);
  } catch (e) {
    // SyntaxError or JSON is undefined.
  if (data.cid) {

if (window.addEventListener) {
  window.addEventListener('message', xDomainHandler, false);
} else if (window.attachEvent) {
  window.attachEvent('onmessage', xDomainHandler);

var alreadySent = false;
function sendHit(cid) {
  if (alreadySent) return;
  alreadySent = true;
  // The normal ga snippet.  If cid exists, it will overwrite any existing cookies.
  var params = {};
  if (cid) params['clientId'] = cid;
  ga('create', 'UA-XXXXX-Y', 'auto', params);
  ga('send', 'pageview');
if (!window.postMessage) {
  // No postMessage Support.
} else {
  // Tell top that we are ready.
  top.postMessage('send_client_id', topOrigin);
  // Set a timeout in case top doesn't respond.
  setTimeout(sendHit, 100);

The sendHit function will only execute once and will automatically fire after 100ms have passed. This is needed if the top level page takes too long to fire or is mis-configured.

We retrieve the clientId from the top level page in the same manner described in getClientId.

iFrames, Cookies, and Data Quality

Certain browsers have issues with cookies and iFrames. Some browsers will not set cookies in iFrames and other browsers require complex, cryptic, headers to be set in order for cookies to work. These iFrame cookie issues can lead to data quality issues.

For iFrames that load a single page and do not allow users to navigate to different pages, developers can turn off cookie storage completely, preventing browser cookie issues. For this to work, developers must use cross-domain tracking linking parameters on the URL of the iFrame.

When you create a tracker in an iFrame, you can turn off cookies storage by setting the storage configuration parameter to none.

ga('create', 'UA-XXXXX-Y', {
  'allowLinker': true,
  'storage': 'none'

To simplify the cross domain linking process, we've developed the autoLink plugin to automatically implement cross domain linking across all the links on a page. Now site owners have a simple way to implement cross domain linking.

Say you have a website hosted on source.com and you have the following links pointing to the following destination domains that you want to track with cross domain tracking:

  • destination.com
  • test.destination.com/page2
  • test.dest3.com?page4

To add cross domain linking to these 3 links you would use:

// Load the plugin.
ga('require', 'linker');

// Define which domains to autoLink.
ga('linker:autoLink', ['destination.com', 'dest3.com']);

In this code, the autoLink plugin is first loaded. Next, the linker:autoLink command is called and is passed an array of domain substrings on which to trigger cross domain linking.

Instead of string matching, you can also use Regular Expressions. The following example will match example.com and example.net, but will not match example.org or subdomains like www.example.com or blog.example.net.

// Define which domains to autoLink using a regular expression.
ga('linker:autoLink', [/^example\.(com|net)$/]);

Finally, on each of the destination domains, each page's existing create method must be updated by setting the allowLinker configuration parameter to true:

ga('create', 'UA-XXXXXX-X', 'auto', {
  'allowLinker': true

When autoLink runs on the source page, an event listener will be added to the document body. Any time a user:

  • Depresses a mouse button (mousedown)
  • Releases a key (keyup)
  • Presses the screen (touchstart)

The autoLink plugin will run and check if the event came from a link that points to one of the domains defined in the array of domain substrings. If the link matches, the autoLink plugin will decorate the link with linker parameters. When the user completes their action, the browser will complete the default behavior and the linker parameter will be sent to the destination domain.

Finally, on the destination domain when allowLinker is set to true, analytics.js will check the URL to see if a valid linker parameter exists and extract the proper values it needs.

Setting linker parameters in the anchor

Advanced users can also pass the linker parameters in the anchor portion of the URL (instead of the query portion) by passing true as an additional parameter to the autoLink command.

// Configure auto-linking to use anchor params.
ga('linker:autoLink', ['destination.com', 'dest3.com'], true);

Cross Domain Auto Linking for Forms

Universal Analytics also includes the ability cross domain link <form> elements automatically.

ga('linker:autoLink', ['destination.com', 'dest3.com'], false, true);

It will decorate all forms that exist when the command is executed. It works on both POST and and GET forms.

The useAnchor parameter has no effect on forms.

Implementation Considerations

Please keep the following in mind while implementing cross-domain features:

Impact on Unique Users

The goal of linker is to attribute the same user/browser to the same user across multiple domains. This means that fewer sessions will be double-counted. As a result, you should expect your unique user count to go down.

A combination of measures are used to prevent multiple users from acquiring the same Client ID used by the linker, which might happen when a URL is shared between users over email, for example. These measures include hashing environmental data (user-agent string and other data), enforcing a 2 minute timeout for each Client ID, and the use of a checksum to ensure the ID was not changed.

None of these will stop a bad actor from sending malicious links, which is why the default for allowLinker is false.

Impact on Previous Sessions and Data

Once the linker accepts a Client ID, the previous Client ID will be thrown away. Only one Client ID is supported and there is no way to join two sessions together. For example:

  1. A user visits source.com.
  2. The same user visits destination.com.
    • The user now has two seperate Client IDs stored as cookies on both domains.
  3. On source.com, the user clicks a link to destination.com and it accepts the linker parameters.
    • The original Client ID on destination.com is deleted and replaced with source.com's Client ID.
    • All subsequent hits sent to Google Analytics will be attributed to the same Client ID from both domains.

This is normally fine for the case of a 3rd-party shopping cart site. Any previous sessions are with another website which won't have previous session information within your account. More generally, linker works well if you have one primary domain that contains outbound links to a secondary domain, but the secondary domain does not use linker to send the user back. As users move between the primary and secondary domain, the session will remain the same, while reducing the corruption of user cookies on the primary domain.

Multiple Properties, Multiple Trackers

Both domains need to use the same GA property in order for cross-domain tracking to work correctly. If the sites use different properties, no session information will be shared and cross-domain tracking will not work.

Cross-domain tracking supports multiple trackers, but be aware that they will all share the same Client ID used by the linker.

The _ga Url Query Parameter

linker uses _ga to store the Client ID used for cross-domain tracking. Your application must not use this parameter for any other purpose. At this time, there is no way configure a different url parameter for use with cross-domain tracking.