NOTE! This solution has been upgraded, and the new approach can be found here.

If you’re unfamiliar with the lingo, cross-domain tracking is a hack used by Google Analytics to circumvent the web browser’s same-origin policy. Essentially, the policy dictates that browser cookies can only be shared with a parent domain and all its sub-domains. In other words, and do not share cookies.

Since Google Analytics calculates sessions and users by using a cookie, this is problematic. If the user navigates from to, they will, by default, browse in a different session (often a different user as well) from that on

There are some great guides out there for implementing cross-domain tracking, and I want to highlight the Bounteous article as well as the Knewledge tutorial. Both have excellent coverage of this difficult topic, and Knewledge have devoted a lot of words for iframe tracking as well.

In this article, I want to go over iframe tracking once again, and update the method to GTM V2. We’ll be using the hitCallback method to decorate the iframe properly with cross-domain tracking parameters, to ensure that any tracking that takes place within is attributed to the original session (and user).


The Simmer Newsletter

Subscribe to the Simmer newsletter to get the latest news and content from Simo Ahava into your email inbox!

Quick note on SUB-domain tracking

Remember, cross-domain tracking applies only to separate parent domains. If you want to track traffic across sub-domains, you don’t need cross-domain tracking. Instead, you just need the cookieDomain field set in your Tags and you’re good to go. Luckily some visionary has already written a simple guide about this.

Custom JavaScript Variable

To make it all work, all you’ll need is a Custom JavaScript Variable. Well, and a simple tweak to your Page View Tag. And some testing. And good luck. But anyway, the Custom JavaScript Variable is what patches everything together.

We’ll use the Variable in the hitCallback field of the very first Tag that fires on your site (should be the Page View Tag). This callback function will reload any iframe of your choice with linker parameters, meaning the Tags in the iframe will run nicely as part of the ongoing session.

Linker parameter is basically your browser’s unique client ID (with some other stuff), attached as a query parameter to the iframe src value. This is then picked up by the tracker in the iframe and used to recreate the _ga cookie in the iframe. That’s how Google Analytics’ cross-domain hack works!

So, to get started, create a new Custom JavaScript Variable, and name it intelligently. I’ve used {{JS - hitCallback for X-Dom iframe}}, but you can use something less poetic if you wish. Here’s what it looks like:

function() {
  return function() {
    try { 
      var gobj = window[window.GoogleAnalyticsObject];
      var iframe = document.querySelector('#myIframe');
      var tracker, linker;
      if (gobj) {
        tracker = gobj.getAll()[0];
        linker = new window.gaplugins.Linker(tracker);
        iframe.src = linker.decorate(iframe.src);
    } catch(e) {}

So, time for a line-by-line walkthrough.

function() { ... }

On the first line, you’re declaring a basic GTM JavaScript function, which needs to be an anonymous function. Nothing special here, moving on.

return function() { ... }

Next, you’re returning another function, as hitCallback requires a function as a parameter for it to work correctly. Also, by returning a function, we’re avoiding unsolicited side effects which would result if you just executed global DOM methods in the outer function. Since we’re operating in a closure, we’re restricting the execution of this function to only the single time it’s called, which is when the hitCallback is executed at the end of the Google Analytics payload dispatch process.

try { ... } catch(e) {}

We’re wrapping the code with a try...catch because that’s simply a good practice. You can add some debugging code into the catch(e) {} block if you wish, but I usually just let it sizzle down silently.

var gobj = window[window.GoogleAnalyticsObject];
var iframe = document.querySelector('#myIframe');
var tracker, linker;

The following three lines setup some variables. First, we’re setting gobj to the global Google Analytics function name. This is again just a good practice, since there are cases where you want to change the function name from ga to something else. With this one-liner, we’re making sure we’re always referring to the correct object.

The next line is important. This is where you’ll retrieve the iframe element you want to modify. I’m using a simple CSS selector to find my iframe (an element with the ID myIframe), so remember to change it to reflect whatever you want to target. You can run this code on multiple iframes if you wish. You just need to loop through each iframe, and run the linker.decorate() method on each.

We’re also declaring tracker and linker as utility variables, which we’ll use in a bit.

if (gobj) {
  tracker = gobj.getAll()[0];
  linker = new window.gaplugins.Linker(tracker);
  iframe.src = linker.decorate(iframe.src);

The final part of the code is where the magic happens. Checking for the existence of the global Google Analytics object is just another good practice you might want to employ.

Next, we initialize tracker with the first Google Analytics tracker created on the page. As I mentioned before, this should optimally be the tracker created by your Page View Tag. We use the getAll() function call to retrieve all the trackers created on the page, and then pick the first one with [0].

On the following line we use the Google Analytics Linker plugin, initializing it with the retrieved tracker. This plugin is what we’ll use to decorate the iframe src with the correct cross-domain parameters. The plugin has a utility method decorate, which takes a URL string as its parameter.

In other words, you’re updating the iframe’s src value with a decorated URL, and this decoration actually adds the correct cross-domain parameters to the URL.

Once you’ve written all this code, remember to save the Variable.

Finally, you need to add this new Variable into the hitCallback field of your Page View Tag.

So, open the Tag, browse to More Settings -> Fields to set, and add a new field:

There we go! We have now successfully set the stage for one of nature’s most spectacular ev… Sorry, been watching too much Planet Earth.

What will happen

Here’s the process of what happens when you next browse the site:

  1. The Page View Tag fires, and after it has completed, its hitCallback field is executed

  2. Since this field had our custom function returned, the code within is run

  3. This code takes the tracker that was just created, and decorates the iframe(s) of your choice with cross-domain tracking parameters

  4. The iframe will reload (because its src value changed) with the cross-domain tracking parameters

What else you’ll need

Now, any Tags firing in the iframe should have the allowLinker : true field set. You can find instructions for this in the Bounteous guide, for example. This simple little field will ensure that the Universal Analytics library looks for cross-domain tracking parameters in the URL, and uses them to set up the tracker in the iframe.

In other words, we’ve circumvented the crippling effect of same-origin policy on cookie sharing by, well, sharing a cookie value as a URL parameter. The wonderfully robust Universal Analytics library takes care of the rest.

Also, you will want to defer the Page View Tag from firing on the landing page of the iframe. Why? Because if you didn’t, the iframe might send a Page View when it’s first loaded, and then again when the iframe is reloaded with the hack. By preventing the iframe from firing on any page whose referrer is not the iframe URL and when the page is in an iframe, you’ll prevent double- or triple-counting your page views in the iframe. For subsequent page views you’d want to fire the Tag, because it reflects actual browsing behavior.

Also, if you look at the Knewledge tutorial, they have a nice solution for actually not loading the iframe in the first place, and only loading it when the hitCallback is executed (or if there’s some problem with loading the JavaScript).

It’s all up to you, of course. Double- or triple-counting isn’t always a problem.

If you have a situation where the iframe isn’t in the DOM in time, you might also need to add a setTimeout in the hitCallback to allow time for the DOM to complete. However, this is very rare in my experience.

Anyway, working with iframes is quite difficult, as you can probably guess. Lots of things to pay attention to.

That was a perfect segue to the final chapter and a well-deserved rant.


This was a rather simple guide for setting up iframe tracking with cross-domain parameters. Let me be clear and on-the-record about something:


They are horrible, Lovecraftian subterranean beings of horror that crawl from the nether pits of darkness after the tolling of the midnight bell. They are playground bullies; entitled little brats that wreak havoc on innocent markup. They are artefacts of human laziness, true examples of Conway’s Law in motion. They are, in essence, untrackable little shit-monsters that exist in the void between websites, accountable to none and hazardous to all.

I can personally attribute some 45% of my hair loss to working with iframes, and they have ruined my day far too many times to call it coincidence.

If you are forced to work with iframes, cross-domain tracking might be the least of your worries.