Tracking Cross-Domain Iframes - Upgraded Solution

An upgraded solution (using customTask) to tracking iframes cross-domain when using Google Tag Manager and Google Analytics.

**Last updated 18 September 2020: Due to how most browsers now have third-party cookie protections in place, this solution will be very ineffective going forwards. You should instead take a look at a cookieless solution.

Some years ago, I wrote a post on how to track cross-domain iframes when using Google Tag Manager and Google Analytics. That solution relied on hitCallback to decorate the iframe, and now that I look back on it, it has its shortcomings.

For one, the older solution used hitCallback which, while being reliable in that Google Analytics has definitely loaded with the linker plugin when the method is called, doesn’t take into account the possible race condition of the script running before the iframe has been loaded.

An additional problem was that the older solution simply grabbed the linker from the first Google Analytics tracker object on the page. But this might not be the one we want to use for the cross-domain tracking.

In this solution, we’ll use the wonderful customTask to decorate the target iframe. The customTask leverages a setInterval() script which polls the page periodically until the target iframe is found or a timeout is reached.

Naturally, this script has been added to my customTask Builder tool.

The customTask itself

This is what the customTask looks like. To deploy it, make sure you read the instructions in the customTask Builder tool before copy-pasting the required code into your page JavaScript (if using the Universal Analytics on-page snippet) or a Custom JavaScript variable (if using Google Tag Manager).

function() {
  var iframeDecorator = {
    selector: 'iframe#decorateMe',
    attempts: 10,
    intervalMs: 1000,
    useAnchor: false
  };

  // DO NOT EDIT ANYTHING BELOW THIS LINE
  var globalSendHitTaskName   = '_ga_originalSendHitTask';

  return function(customTaskModel) {

    window[globalSendHitTaskName] = window[globalSendHitTaskName] || customTaskModel.get('sendHitTask');
    var tempFieldObject, dimensionIndex, count, ga, tracker, decorateTimer, decorateIframe, iframe;

    if (typeof iframeDecorator === 'object' && typeof iframeDecorator.selector === 'string' && typeof iframeDecorator.attempts === 'number' && typeof iframeDecorator.intervalMs === 'number') {
      count = 0;
      ga = window[window['GoogleAnalyticsObject']];
      tracker = ga.getByName(customTaskModel.get('name'));
      decorateIframe = function() {
        iframe = document.querySelector(iframeDecorator.selector);
        if (iframe !== null && /[?&]_ga=/.test(iframe.src)) {
          window.clearInterval(decorateTimer);
          return;
        }
        if (iframe === null) {
          count += 1;
          if (count === iframeDecorator.attempts) {
            window.clearInterval(decorateTimer);
          }
          return;
        }
        window.clearInterval(decorateTimer);
        iframe.src = (new window.gaplugins.Linker(tracker)).decorate(iframe.src, iframeDecorator.useAnchor);
      };
      decorateTimer = window.setInterval(decorateIframe, iframeDecorator.intervalMs);
    }

  };
}

To begin with, make sure to edit the configuration object iframeDecorator. Set the parameters to their correct values.

Parameter Example value Description
selector "#decorateMe" String with the CSS selector that matches the iframe you want to decorate.
attempts 10 Number denoting how many times the page is polled for the iframe until the script stops trying.
intervalMs 1000 Milliseconds between each attempt to find the iframe on the page.
useAnchor false Whether to use a hash (#) instead of a URL query parameter to append the linker parameter to the iframe src.

Once you’ve edited these, you’ll need to add this customTask to your trackers or tags.

Make sure to add this customTask to a hit or tag that fires after (or shortly before) the iframe element has been added to the page. The script has a timeout which is attempts * intervalMs milliseconds, so if the script doesn’t find the iframe by this timeout, the script will no longer poll the page, and the iframe will remain undecorated.

For example, if the iframe is in the page HTML template itself, it’s typically enough to add this customTask to the Page View hit that fires as soon as the page starts to load (so All Pages trigger in GTM, or the on-page snippet if using Universal Analytics).

But if the iframe is added in a modal upon the click of a button, for example, you’ll want to fire a Google Analytics hit with that click, and then make sure to include this customTask in the hit.

How it works

When the hit to Google Analytics is generated, this customTask repeatedly asks the page: “Is there an iframe that matches the given CSS selector?”. If this iframe is found before the timeout (attempts * intervalMs), then the iframe path is decorated with the cross-domain linker parameter.

This, in turn, means that when the page within the iframe loads, the URL of the page will have the _ga= cross-domain linker parameter, which is then utilized by the first tag that fires in the iframe with the allowLinker flag set to true.

Caveats

Note that this only decorates the iframe src path. This is the only thing you can do from the source site, so if it doesn’t work, you’re out of luck with this solution. Some iframes might introduce a redirect when the original src is loaded, which often eliminates the query parameter from the URL. If this is the case, you can try setting the useAnchor parameter to true, which might be more resilient to redirects.

If you’re interested in a viable alternative to the linker parameter, take a look at the postMessage API introduced in this great article by Dan Wilkerson from Bounteous.

Be wary, also, that this customTask uses the cookie configuration of the hit that the customTask was added to. So if this hit has an exceptional cookie configuration, it’s possible the linker is built with the wrong Client ID.

In other words, make sure you are aware of any modifications to the default cookie settings in the tracker, hit or tag to which the customTask is added.

There is no real penalty to adding this customTask directly to the tracker or all your Universal Analytics tags. If the iframe already has a cross-domain parameter in the src value, the script simply stops attempting to decorate the iframe.

Summary

This is an alternative approach to my original solution of using hitCallback to decorate the iframe. In my humble opinion, I think this customTask wipes the floor with my original idea, so I’m fully recommending using this instead.

My feelings on iframes haven’t really changed since writing the original article, so I’ll just include the relevant excerpt here for you to sympathize with.

If you want to vent, please let me know what you think about iframes in the comments of this article.