This guide describes how to track users across domains (cross domain tracking) using analytics.js.
Overview
The analytics.js library uses a single first-party cookie to store the Visitor 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:
- On the source domain, retrieve the cookie data and append the data as a query parameter to all links pointing to the destination domain.
- 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.
- 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:
182119591.1441315536
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-XXXX-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:
_ga=1.182119591.1441315536.1362115890410
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-XXXX-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:
//destination.com?_ga=1.182119591.1441315536.1362115890410
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:
//destination.com#_ga=1.182119591.1441315536.1362115890410
Decorating HTML Links
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"> </form>
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 totrue
.
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> <script> 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); document.getElementById(divId).appendChild(iFrame); }; } // Dynamically add the iFrame to the page with proper linker parameters. ga(addiFrame('myiFrame', 'destination.html')); </script>
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-XXXX-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 advantagous beacuse it does not block the execution of the iframe.
Source page
<script> // The normal snippet. ga('create', 'UA-XXXX-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; break; } } 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) { tracker.get('clientId'); 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.attacheEvent('onmessage', xDomainHandler); } </script>
On the iframe page
<script> 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) { return; } try { var data = JSON.parse(event.data); } catch (e) { // SyntaxError or JSON is undefined. return; } if (data.cid) { sendHit(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-XXXX-Y', 'auto', params); ga('send', 'pageview'); } if (!window.postMessage) { // No postMessage Support. sendHit(); } 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); } </script>
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-XXXX-Y', { 'allowLinker': true, 'storage': 'none' });
Cross Domain Auto Linking
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.
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 Visitors
The goal of linker
is to attribute the same user/browser to the same visitor across
multiple domains. This means that fewer visits will be double-counted. As a result, you should
expect your unique visitor count to go down.
A combination of measures are used to prevent multiple visitors from acquiring the same Visitor 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 visitor 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 Visitor ID, the previous Visitor ID will be thrown away. Only one Visitor ID is supported and there is no way to join two visits together. For example:
- A user visits
source.com
. - The same user visits
destination.com
.- The user now has two seperate Visitor IDs stored as cookies on both domains.
- On
source.com
, the user clicks a link todestination.com
and it accepts the linker parameters.- The original Visitor ID on
destination.com
is deleted and replaced withsource.com
's Visitor ID. - All subsequent hits sent to Google Analytics will be attributed to the same Visitor ID from both domains.
- The original Visitor ID on
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 visit 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 visit and session
will remain the same, while reducing the corruption of visitor 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 Visitor ID used by the linker.
The _ga
Url Query Parameter
linker
uses _ga
to store the Visitor 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.