I really like Google Optimize. It has a fairly intuitive UI, setting up experiments is easy, and there’s integrations for both Google Tag Manager and Google Analytics built into the system. It’s still a JavaScript-based, client-side A/B-testing tool, so problems with flicker and asynchronous loading are ever-present (though this is somewhat mitigated by the page-hiding snippet).

One issue with the Google Analytics integration is the difficulty of creating segments for sessions where the users were actively participating in the experiment. In fact, there’s no specific signal in Google Analytics that tells you the NOW the user is “experiencing” an experiment. You’ll find the Experiment Name and Experiment ID dimensions, yes, but they have one drawback:

The Experiment Name and Experiment ID dimensions are user-scoped for the duration of the experiment!

So if the user takes part in an experiment, all their hits and sessions from that moment on until the experiment is over will be annotated with the Experiment Name and Experiment ID values.

But I want to know exactly and only the sessions where my users were seeing the experiment content. I want to use this information to create segments where I can view other conversion goals or funnels than those configured into Optimize. This is increasingly important if you want to use Optimize for some quick personalization proofs-of-concept, since then it’s vital to know how the user reacted during the session when they saw the change in the content.

Anyway, to make this whole thing work with the rarified analytics.js snippet and Google Tag Manager, we will use a feature I have seldom written about: customTask. OK, I’ve written about it plenty.

With customTask, we can automatically add a session-scoped Custom Dimension to all hits that took part in an experiment, and in some edge cases we’ll also send an event to GA to make sure the data is carried over.

How it works

When you land on a page where an Optimize experiment is running (i.e. the page is (one of) the target(s) of the experiment), your Google Analytics hits will contain the &exp; parameter with a value consisting of all the experiment IDs and variant IDs the user is participating in:

This key is what GA then uses to attribute the user to these particular experiments. So once that hit reaches GA, my Client ID will be associated with those two experiment IDs until the experiment is over.

Now, what I actually want to happen is for a Custom Dimension to be added to that Page View hit if it has the &exp; key. For this, I need to use customTask so that it can sniff the requests to Google Analytics, and in case they contain this key, dynamically add the Custom Dimension parameter with the experiment string. That way I’ll have the experiment data sent to Google Analytics nicely in the Custom Dimension of my choice!

If you’re using Google Tag Manager to deploy Google Optimize, the same code will apply, but instead of leveraging a Page View hit, the Optimize tag will use a special hitType === 'data' hit, which takes your experiment data to GA.

Unfortunately for us, this data hit type is not exposed in Google Analytics, so we can’t use that to segment our visitors, nor does piggy-backing it with a Custom Dimension do anything. So, we need to configure Google Tag Manager to actually send an extra event when a data hit is encountered that also has the &exp; parameter with it.

Implement via analytics.js

If you’re using the analytics.js snippet, here’s what you need to do:

<script>
  // Analytics.js snippet
  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

  ga('create', 'UA-XXXXXX-Y','auto')

  // NEW: Add customTask field to tracker
  ga('set', 'customTask',  function(model) {

    // Change this to the Custom Dimension index to which you want to send the experiment data!
    var customDimensionIndex = '6';
    
    // Make sure the new hit is only generated once (thanks Vibhor Jain!)
    var hasNewHitBeenGenerated = false;
    
    var globalSendTaskName = '_' + model.get('trackingId') + '_sendHitTask';
    
    var originalSendTask = window[globalSendTaskName] = window[globalSendTaskName] || model.get('sendHitTask');
    
    model.set('sendHitTask', function(sendModel) {
      var ga = window[window['GoogleAnalyticsObject']];
      var hitPayload = sendModel.get('hitPayload');
      if (sendModel.get('exp')) {
        if (sendModel.get('hitType') === 'data' && !hasNewHitBeenGenerated) {
          var tracker = sendModel.get('name');
          originalSendTask(sendModel);
          ga(tracker + '.send', 'event', 'Optimize', sendModel.get('exp'), {nonInteraction: true});
          hasNewHitBeenGenerated = true;
          return;
        }
        if (hitPayload.indexOf('&cd' + customDimensionIndex + '=') === -1) {
          sendModel.set('hitPayload', hitPayload + '&cd' + customDimensionIndex + '=' + sendModel.get('exp'), true);
        }
      }
      originalSendTask(sendModel);
    });
  });
  // NEW BLOCK ENDS

  ga('require', 'GTM-XXXXXX');
  ga('send', 'pageview');
</script>

The change to the original Optimize-modified analytics.js snippet is the entire block starting with // NEW:... and ending with // NEW BLOCK ENDS.

This code listens to all GA hits sent with the default tracker, and if they have the &exp; parameter, then the Custom Dimension is dynamically added to the hits, along with the experiment ID string the user is associated with. Note that you must change the value of var customDimensionIndex to reflect the Custom Dimension index you’ve created in Google Analytics. I prefer to use a Session-scoped Custom Dimension, but you could use hit-scoped, too, for increased granularity.

That’s all you need to do with analytics.js. On pages not included in the experiment, no Custom Dimension or any extra hit is sent. Business will be as usual.

Implement via Google Tag Manager

In Google Tag Manager, you need to create a new Custom JavaScript variable, and give it a name like {{JS - customTask - Optimize experiment}}. The variable should look like this:

function() {
  return function(model) {
    
    // Change this to the Custom Dimension index to which you want to send the experiment data!
    var customDimensionIndex = '6';

    // Make sure the new hit is only generated once (thanks Vibhor Jain!)
    var hasNewHitBeenGenerated = false;
    
    var globalSendTaskName = '_' + model.get('trackingId') + '_sendHitTask';
    
    var originalSendTask = window[globalSendTaskName] = window[globalSendTaskName] || model.get('sendHitTask');

    model.set('sendHitTask', function(sendModel) {
      var ga = window[window['GoogleAnalyticsObject']];
      var hitPayload = sendModel.get('hitPayload');
      if (sendModel.get('exp')) {
        if (sendModel.get('hitType') === 'data' && !hasNewHitBeenGenerated) {
          var tracker = sendModel.get('name');
          originalSendTask(sendModel);
          ga(tracker + '.send', 'event', 'Optimize', sendModel.get('exp'), {nonInteraction: true});
          hasNewHitBeenGenerated = true;
          return;
        }
        if (hitPayload.indexOf('&cd;' + customDimensionIndex + '=') === -1) {
          sendModel.set('hitPayload', hitPayload + '&cd;' + customDimensionIndex + '=' + sendModel.get('exp'), true);
        }
      }
      originalSendTask(sendModel);
    });
  };
}

It’s pretty much exactly the same code you would use in the analytics.js.

Then, go to your Google Optimize tag, and scroll down to Fields to set. Add a new field with:

Field name: customTask
Value: {{JS - customTask - Optimize experiment}}

So that it looks like this:

And that should be it. Now when you browser the experiment pages, you should see a hit with type 'data' being sent, and immediately after that an event hit which looks like this:

Summary

I really wish that the Optimize / Google Analytics integration would give us a hit or dimension we could use to segment sessions that actively participated in an experiment. Right now you either need to replicate the test conditions in a segment (and if you sample only a part of your visitors even this won’t cut it), or use a solution like the one described in this article.

Even if this were superfluous and unnecessary, I’m giddy with excitement to show yet another cool use case for customTask. I’m fairly certain that if we hadn’t already given our baby boy a gorgeous name, the world would have come to know him as customTask Ahava. For now, it will have to do as his nickname. That’s how much I love customTask!