Universal Analytics Plugins Explained

Guide to how plugins work in Google Analytics.

There are many tools and methods to make Google Analytics more manageable. Google Tag Manager is probably the best known of these, and you can find many, many articles about GTM on these pages as well.

However, today I want to tell you about one of the features of Universal Analytics that hasn’t, as far as I know, received too much attention. It’s funny, because at the same time almost everyone uses the feature in the shape of eCommerce, enhanced link attribution, and cross-domain tracking. I’m talking, of course, about Universal Analytics plugins.

Plugins are JavaScript objects, which you can load and execute using the Universal Analytics global object interface. This is accessed, by default, using the ga() function.

Plugins allow you to do a number of useful things:

  1. Decouple tracker object access from JavaScript embedded in the page HTML, allowing you to crete, execute, and maintain often used ga() commands in library files instead of directly in the page

  2. Quickly access the tracker object of choice, without having to prefix every command with the tracker name, or to loop through all the existing trackers to find the correct one

  3. Execute the plugin commands synchronously, which means you can be 100 % sure that anything the plugin does will be available to commands that come after it in the command queue

All the benefits listed above facilitate a plug-and-play integration between a platform and Google Analytics. If you’re the developer of the platform, you can create a plugin that does most of the legwork, such as assigning a new Custom Dimension with its respective value, and the user only has to load the plugin to reap the benefits.

How plugins work

Before getting into a technical description of plugins themselves, there’s something we need to clear up about the Universal Analytics tracker object first. Here’s a typical tracking code on a typical website:

<script>
  (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','//www.google-analytics.com/analytics.js','ga');

  ga('create', 'UA-1234567-1', 'auto');
  ga('send', 'pageview');

</script>

If you want to understand what the code above does statement by statement, this and this are good places to start.

What’s important, however, is that the analytics.js library is loaded asynchronously. The loading starts when the immediately invoked function expression (IIFE) is executed, but the browser’s JavaScript engine moves right to the two ga() commands before the analytics.js library has loaded. This means that the two ga() calls are not actually interacting with analytics.js, but instead they are pushing commands into a command queue, which is processed first-in first-out as soon as the library has loaded.

This, in turn, has a significant implication on what you can do with the code. Consider the following example:

<script>
...the IIFE here...

ga('create', 'UA-1234567-1', 'auto');
ga('send', 'pageview', {
  'dimension1' : ga.getAll()[0].get('clientId')
});
</script>

If you know your tracker methods, you’ll see that this attempts to send the clientId of the tracker object as a Custom Dimension with the page view hit.

This code will, however, result in the error: ga.getAll is not a function. The reason for this error is due to the library not having loaded yet when the JavaScript engine processes the command in the ga() call. It’s the analytics.js library that sets up the interface for the global object, not the tracking code.

There are two ways to overcome this. You can pass a callback function as an argument to the ga() call, and any code within that function will not be executed until the library has loaded. The second method would be to use a plugin.

When you call the ga('require', 'pluginName') method, this request is added to the command queue. When the analytics.js library has finally loaded, the command queue will be processed in order. Once the processing reaches this ‘require’ command, the queue will halt until a ‘provide’ call with the same plugin name is found in or added to the queue. This means that the following code would work nicely:

<script>
...the IIFE here...

ga('create', 'UA-1234567-1', 'auto');
// Load the getClientId plugin, which gets the
// clientId and sets it as dimension1 on following hits
ga('require', 'getClientId');
ga('send', 'pageview');
</script>

Even though the tracker interface is not available when the ‘require’ command is added to the queue, the interface will be available once the queue is being processed.

How to create a plugin

Let’s create the getClientId plugin from the previous chapter. The plugin code itself consists of two required components:

  1. A constructor that is executed when the plugin is loaded

  2. A ‘provide’ command that links the constructor with the plugin name

To create the plugin getClientId, you would need something like the following code loaded on the page:

var GetClientId = function(tracker) {
  tracker.set('dimension1', tracker.get('clientId'));
};
var ga = window[window['GoogleAnalyticsObject'] || 'ga'];
if (ga) {
  ga('provide', 'getClientId', GetClientId);
}

The first lines are for the constructor. It’s just a JavaScript function, but it turns into an object constructor with the ga('require', 'getClientId') call, which creates a new object instance of GetClientId.

If object-oriented JavaScript confuses you, take a look at the excellent JavaScript track at codecademy.

Anyway, the tracker object used in the call to the constructor is automatically passed as an argument to the function. This means that you can run commands on the tracker object, and you don’t have to worry about interacting with the right tracker or sending the hits to the right Google Analytics property.

The next lines first make sure that the ga() command exists. The ga() function is created in the Universal Analytics tracking snippet, so if your plugin code is executed before the browser parses the tracking snippet, it might not work.

Finally, the ga('provide', 'pluginName', Constructor) command is run so that the plugin can be found with its respective ‘require’ call.

To briefly recap, here is how plugins are loaded and linked to trackers:

  1. The Universal Analytics tracking snippet creates the ga() function and the command queue

  2. All function calls to ga() are added to this command queue

  3. If you have a plugin, both its ‘require’ and ‘provide’ calls will be added to the queue as well

  4. Once the analytics.js library has loaded, the commands in the queue are processed in order

  5. If a ‘require’ command is encountered, the queue processing halts indefinitely, until a ‘provide’ call with the same plugin name is run

  6. The entire plugin constructor function is executed before queue processing continues

Below is an example of the loading order in action. The plugin ‘require’ call is between the ‘create’ and ‘send’ calls, and the library itself is loaded after the Universal Analytics tracking code snippet. This is to secure that the ga() function is created before the plugin.

<script>
...the function expression here...

ga('create', 'UA-1234567-1', 'auto');
ga('require', 'getClientId');
ga('send', 'pageview');
</script>
<script src="https://www.simoahava.com/scripts/getClientId.js" async></script>

Note that the ‘require’ command waits for the respective ‘provide’ command indefinitely. This means that if you misspell the plugin name, or if you only have the ‘require’ command there for some reason, the queue processing will not proceed. So try to avoid breaking your analytics implementation, please!

Example: SimoPlugin library

Here’s an example of a plugin I might be tempted to use over and over again. In it, I have two special features:

  1. A generic event pusher, which I can use with all my trackers to send events to their respective properties

  2. A hit duplicator, which duplicates the hit to the Universal Analytics collection endpoint, and sends it as an HTTP request to any custom endpoint of my choice

I’m storing the plugin in a file called simoPlugin.js, which I then load asynchronously after my tracking code:

<script>
...the IIFE here...

ga('create', 'UA-1234567-1', 'auto');
ga('create', 'UA-1234567-2', 'auto', {'name' : 'rollup'});
ga('require', 'simoPlugin');
ga('rollup.require', 'simoPlugin');
ga('send', 'pageview');
ga('rollup.send', 'pageview');
</script>
<script src="https://www.simoahava.com/scripts/simoPlugin.js" async></script>

As you can see, I have my normal tracker and a tracker with the name rollup, and I’m loading the plugin for both trackers using the typical multiple tracker syntax.

The way the hit duplicator works is that whenever a ‘send’ command is used, e.g. ga('send', 'pageview'), the exact payload to the Universal Analytics endpoint is copied and sent to a (fictional) endpoint of mine at _http://process.simoahava.com/collect_:

By duplicating hits like this, you can create your own data collection mechanism. You can even use a cloud endpoint, which will give you a lot more processing power and scale. You might even use this to overcome the schema conspiracy I’ve been ranting about before.

The generic event pusher works by sending a custom event object to a plugin method called trackEvent:

ga('simoPlugin:trackEvent', {
  'cat' : 'Link Click', 
  'act' : 'Outbound', 
  'lab' : 'http://www.google.com', 
  'di' : 4, 
  'dv' : document.location.pathname
});

As you can see, an object literal is passed as the second argument of the ga() call. This argument, in turn, is passed to the trackEvent method in the plugin code. The method takes the arguments, makes sure no undefined fields are sent, and pushes an event hit to the property associated with the tracker that was used to call the plugin method. If I wanted to send the call to my rollup tracker, I would have used the syntax ga('rollup.simoPlugin:trackEvent').

simoPlugin code

The code stored in the simoPlugin.js library looks like this:

(function() {

  // Assign the ga variable to the Google Analytics global function
  var ga = window[window['GoogleAnalyticsObject'] || 'ga'];

  // Helper function for registering the Plugin
  var providePlugin = function(pluginName, pluginConstructor) {
    if (ga) { 
      ga('provide', pluginName, pluginConstructor);
    }
  };
  
  // Constructor for simoPlugin
  // Copies payload to custom host
  var SimoPlugin = function(tracker) {
    this.tracker = tracker;
    
    // Copy the original hit dispatch function
    var originalSendHitTask = this.tracker.get('sendHitTask');
    
    // Modify the existing hit dispatcher to send a local copy of the hit
    this.tracker.set('sendHitTask', function(model) {
      originalSendHitTask(model); // Send the original hit as usual
      var xhr = new XMLHttpRequest();
      xhr.open('POST', 'http://process.simoahava.com/collect', true);
      xhr.send(model.get('hitPayload'));
    });
  };
  
  // Set up a generic event dispatcher
  SimoPlugin.prototype.trackEvent = function(evt) {
    var c = evt['cat'];
    var a = evt['act'];
    var l = evt['lab'] || undefined;
    var v = evt['val'] || undefined;
    var x = {};
    x['nonInteraction'] = evt['ni'] || false;
    if (evt['di']) {
      x['dimension' + evt['di']] = evt['dv'] || undefined;
    }
    this.tracker.send('event', c, a, l, v, x);
  };
  
  providePlugin('simoPlugin', SimoPlugin);
})();

First things first: the whole thing is wrapped in an immediately invoked function expression. This is a good habit in general, since it will scope all variables within to function scope, and thus you’ll avoid polluting the global namespace. Here’s good overview of why IIFEs rock: I Love My IIFE - Greg Frank.

The next few lines are for making the plugin loader more generic. Since it’s possible to change the global function name in the analytics.js tracking code, we’ll need to make sure the plugin loader works even if the function name is no longer ga.

The providePlugin is a generic helper function which is largely redundant, but it will provide useful if you’re loading multiple plugins in the same library.

The constructor

We create the constructor function in these lines:

var SimoPlugin = function(tracker) {
  this.tracker = tracker;
    
  // Copy the original hit dispatch function
  var originalSendHitTask = this.tracker.get('sendHitTask');
    
  // Modify the existing hit dispatcher to send a local copy of the hit
  this.tracker.set('sendHitTask', function(model) {
    originalSendHitTask(model); // Send the original hit as usual
    var xhr = new XMLHttpRequest();
    xhr.open('POST', 'http://process.simoahava.com/collect', true);
    xhr.send(model.get('hitPayload'));
  });
};

As you can see, the first argument to the function will be the tracker object used to load the plugin. This means that we can manipulate and use this tracker object interface without having to worry about accidentally sending hits to wrong GA properties!

I use this.tracker = tracker; to store a reference to the tracker object into each instance of the plugin created with this constructor. This is why commands like ga('simoPlugin:trackEvent') and ga('rollup.simoPlugin:trackEvent') access a different tracker object. They both have their own, unique bindings of this, and thus they both have their own, unique tracker object references.

The next line accesses the sendHitTask property of the tracker object. Whenever a ‘send’ command is used with the Universal Analytics global function, a series of tasks are executed. sendHitTask is one of these, and it is used to send the hit payload to the Universal Analytics collection endpoint.

The last call in the block is for updating the sendHitTask task in the tracker. This update uses the original sendHitTask (now stored in a variable called originalSendHitTask), and sends the payload to the Universal Analytics collection endpoint as before. The next three lines create a new HTTP request to the endpoint of your choice. The payload of this request is the exactly same payload that is sent to Universal Analytics.

By using this code, you will be able to create a perfect copy of the hit sent to Universal Analytics, and you can send this copy anywhere you want, such as a local hit processor, or a custom endpoint in a cloud server, for example.

Because this code is in the constructor, it is automatically applied to all ‘send’ commands that take place on the page after the plugin has been loaded.

The event pusher

The event pusher is defined as a method of the SimoPlugin object prototype. The reason we’re applying it to the prototype is because we want it to be available to all instances created with the SimoPlugin constructor. If we’d leave the prototype object out of the declaration, the trackEvent method would be added to an object literal called SimoPlugin, and not the object prototype. This means that the instances created from this prototype would not be able to use that method.

Again, if this is confusing, be sure to read up on object-oriented JavaScript. It’s interesting stuff, and it really shows what a complex and powerful programming language JavaScript is!

The event pusher code looked like this:

SimoPlugin.prototype.trackEvent = function(evt) {
  var c = evt['cat'];
  var a = evt['act'];
  var l = evt['lab'] || undefined;
  var v = evt['val'] || undefined;
  var x = {};
  x['nonInteraction'] = evt['ni'] || false;
  if (evt['di']) {
    x['dimension' + evt['di']] = evt['dv'] || undefined;
  }
  this.tracker.send('event', c, a, l, v, x);
};

This code takes an object as an argument. This object is sent with the ga() command when the method is invoked:

ga('simoPlugin:trackEvent', {
  'cat' : 'category',
  'act' : 'action',
  'lab' : 'label',
  'val' : 1,
  'ni' : false,
  'di' : 1,
  'dv' : 'dimensionValue'
});

There are fields for Category, Action, Label, and Value, and I also let you determine whether the hit is non-interaction or not. Finally, you can also add one dimension with the index number and value exposed in the parameters.

Because Category and Action are required fields for events, the code will fail if these are not in the object. All the other fields can be left out, which is why I have them resolve to undefined if they don’t exist in the event object. The exception is the nonInteraction field, which defaults to false.

Using this generic event pusher, it’s pretty trivial to send simple AND complex events to Universal Analytics.

The final line, this.tracker.send(), sends the event using the tracker object’s send() method. As you can see, we’re using this.tracker, which accesses the interface of the tracker object used to call the method. The beauty of object-oriented JavaScript, right here ladies and gentlemen! If we didn’t use the power of object instances like this, we’d need to loop through all the trackers on the page to find the one we want to use. Way too complex!

Platform integrations

Well, if the event pusher didn’t persuade you with its potential, and if you’re left unimpressed by the hit duplicator, another great idea for plugins is to integrate a SaaS platform and Google Analytics together.

For example, let’s say you have a platform through which you can create a new Custom Dimension in a Universal Analytics property, and then you can populate it with some value. This would happen programmatically, and the platform would use the Google Analytics Management API to create the Custom Dimension.

Once the dimension is created, you’d dynamically generate the JavaScript plugin library to include these programmatically created Custom Dimensions. Then you use the plugin commands to send hits to the trackers, using the correct Custom Dimensions.

This makes the integration between the platform and the website very plug-and-play, and you don’t need to ask the users to look up the correct dimension number, add it to the inline code, and risk some silly misunderstanding ruining the integration.

It’s such an elegant way of associating a website with your platform with just two lines of code at best (one for loading the library, and one for the ga('require', 'pluginName') call).

Summary

Plugins are an excellent way of associating often made calls with their respective tracker objects.

Also, they allow you to decouple all of the Universal Analytics commands from inline code. This allows you to write complex handlers for these commands without polluting the page template.

Plugins are, however, quite an advanced use case for data collection. They require a very good understanding of JavaScript and of how the Universal Analytics library works.

Especially if you’re working with platform integrations and want to minimize the amount of work that users willing to implement the plugin need to do, I strongly recommend talking to your developers about creating a custom plugin for the integration.

Unfortunately, plugins are not supported by the Google Tag Manager Universal Analytics tag template yet, so you might have to use a Custom HTML Tag to load plugins with GTM. See this article for inspiration.