This article is my remedy. I’ve run through the Docs, pulled out all the parts relevant to the front end, reorganized them and re-written them into something that is (hopefully) more friendly and human readable. It’s just the way I learn things, and it has the handy side effect of producing these articles that I can share with all you lovely people! So without further ado, I give you my run through:

To start off: that small, weird looking piece of code that we paste into the head. It’s actually quite simple. Here’s a great deconstruction of the ga snippet if you’re curious. All it does is create a global ga object, enqueue any immediate requests, and load in analytics.js. The global also gives us a single place to access the functions in analytics.js once it’s loaded (we’ll get in to them later). That’s it! We can configure the global name if there’s a conflict, but other than that there’s really not much to it.

Now analytics.js, the code that the snippet loads in, that’s where the more interesting things start happening. It’s only a small part of the whole Google Analytics ecosystem (diagram below). You’ll see ga.js there, that’s the old analytics script. We’re using analytics.js which is the new ‘Universal Analytics’. That extra box, the ‘Measurment Protocol’, is what does the talking to the GA servers – and the way they talk isn’t very fun to work with. Fortunately we don’t have to deal with the precise formatting of data that gets passed, we only deal with the friendlier functions provided by analytics.js.

Diagram showing the break down of all the GA technologies

Just before we dive into those friendly functions I think it’s worth having a quick look at the link between us, analytics.js, and the Measurement Protocol. The 30,000 foot view (after everything’s loaded): analytics.js creates a tracker object to hold all the data. We tell analytics.js what data to put into the tracker. When we’re happy, we ask analytics.js to send the tracker’s data – it then runs through a series of tasks that check, build, and send our data… But wait, where’s the Measurement Protocol? Well, as its name suggests, it’s not really a ‘thing’, it’s just how the GA servers like to be talked to and although it’s in its own box in that big diagram, analytics.js is the thing that deals with building the data into the correct format and sending it. If we really wanted to we can look into the way that’s done, but for now, lets leave that alone and get stuck into how we play with the tracker data.

(Quick note, everything that follows happens in JavaScript after the ‘magic snippet’).

The ga object’s methods

This is how we talk to analytics.js. It provides us with a series of functions, three that act on trackers (create, set, and send) and three that help us juggle trackers (getByName, getAll, and get). I’ll look at the latter three first as there’s a lot less to say about them.

get, getByName, & getAll

These guys are only really concerned with handing us tracker data / objects whenever we ask. To access them we pass ga an anonymous function and call them from there. The reason for this is that it allows ga to enqueue our code until after analytics.js has loaded. One last thing before getting into it: generally we only have one tracker but occasionally you may want multiple. The first tracker is set up for us on the tracker variable and has the default name ‘t0’. Any subsequent trackers have to have different names and to get them we use the functions I’m about to demonstrate, how handy!

ga(function() {

  //get a tracker by name
  var myTracker = ga.getByName('T1000');

  //get an array of all the trackers 
  var allMyTrackers = ga.getAll();

  //get data from a tracker object,
  //in this case the default one
  var fieldValue = tracker.get(fieldName);


The other functions: create, set, and send

So the previous three functions found trackers and returned data from within them but didn’t actually do anything with them. These next three functions are where we start interacting with the trackers themselves. Again, to access them, we’re passing stuff to the ga object for the same reason as before, except instead of passing anonymous functions we’re just passing the name of the function we want to use plus a few parameters. analytics.js is pretty flexible with the way we pass these things and each format works for create, set and send:

  • options inline:
ga('function-name', 'option 1', 'option 2', ... );
  • options object:
ga('function-name', {
  'option 1 key': 'option 1 value',
  'option 2 key': 'option 2 value',
  • mixed:
ga('function-name', 'option 1', {
  'option 2 key': 'option 2 value',

You might have noticed we’re not defining which tracker and you may also have guessed (correctly!) that they will apply to the default tracker. To operate on another tracker we have to prepend the tracker’s name to the function’s name as follows:

ga('tracker-name.function-name', ...

If you’re mixing and matching between the different styles, double check the format for each function you’re dealing with. Personally I’ll be sticking with the object option as much as possible so that I can define the values on an object variable before passing that variable to the ga function, but – whatever floats your boat! Now the actual functions:


ga('create', {
  trackingId: 'UA-XXXX-Y',
  cookieDomain: 'auto', //recommended
  name: 'T1000' //Optional, if you want multiple trackers, defaults to 't0'

Finally we have an actual tracker! Usually we won’t be needing a second tracker, so ‘name’ will generally not be included. There’s a lot more information we can add to a tracker. We don’t need to worry about much of it yet, but it’s worth noting that the first section in that link lists the fields that can only be set by this create command. Everything else can be added later.


ga('set', {
  page: '/about',
  title: 'About Us'

For any changes we want to make to the data held within the tracker, or for any data we want to add. As mentioned before, there are a whole bunch of fields that can be set on a tracker. You might prefer to add them all in when you initially create the tracker but for those of us who deal with single page apps, the ability to set these properties is a really handy thing! Trackers do not update themselves, so we have to hold their hands through any changes that we want measured.


ga('send', {
  'hitType': 'pageview',       // Required.
  'eventCategory': 'button',   // Required.
  'eventAction': 'click',      // Required.
  'eventLabel': 'nav buttons',
  'eventValue': 4

With the tracker created and all the relevant data added, it’s time for it to leave home and head out to the GA servers for collection. The first parameter here, hitType, can be: “pageview”, “event”, “social”, “timing”, or “exception”. They’re probably what you think they are. Each has its own requirements for the details object that we pass, but we’ll get into them in a bit. The next two params in the example, “eventCategory” and “eventAction”, just happen to be the requirements that go with “pageview” (and “event”). Finally, the last two params are just examples of optional extras. We can add the same additional fields to this object as we can to the one passed to set. The difference is that those passed to send will not be saved to the tracker, they live once with this hit then disappear when it heads off. set saves data to the tracker, send saves data to this hit.

Hit types

As mentioned above we’ve got “pageview”, “event”, “social”, “timing”, and “exception”. These allow us to define in a little more detail the type of thing that’s happening on our site.

hitType: pageview

ga('send', {
  'hitType': 'pageview',      // Required.
  'page': '/page-override',   // set by default
  'title': 'title override',  // set by default
  ...                         // any of the general fields

Generally speaking this type of hit will be sent as soon as the page loads. For single page apps we’ll have to define the page in a more interesting way as interactions can happen in much smaller modular ways, but that’s something to work out per project. The ‘page’ value is pulled in by default, so we don’t usually need to worry about it; it’s in the example to show we can override it if we want to. The same goes for the ‘title’ value – it defaults to the value of document.title.

That last “…” is just a place holder in these examples to show we can add in any of these general field values.

hitType: event

ga('send', {
  'hitType': 'event',          // Required.
  'eventCategory': 'button',   // Required.
  'eventAction': 'click',      // Required.
  ...                          // any of the general fields

This is for tracking all the extra custom stuff that might happen on your site. Button clicks, game events, pretty much anything you can latch onto and watch through JavaScript.

hitType: social

ga('send', {
  'hitType': 'social',                      // Required.
  'socialNetwork': 'facebook',              // Required.
  'socialAction': 'like',                   // Required.
  'socialTarget': '',  // Required.
  ...                                       //any of the general fields

This is for Facebook, Twitter, Google+ interactions (likes etc). It standardizes the way we collect this type of data and as a result, makes life much easier (for the analytics people) when it comes to comparing data across different social networks.

hitType: timing

ga('send', {
  'hitType': 'timing',          // Required.
  'timingCategory': 'jQuery',   // Required.
  'timingVar': 'Load Library',  // Required.
  'timingValue': 20,            // Required.
  'timingLabel': 'Google CDN',
  ...                           //any of the general fields

analytics.js already times a few things for us, but we can add in additional timings with this hit type. Keep in mind the actual measuring of any extra timings is up to us. Fortunately the guys at Google have already worked out a function that times the loading of an external js file (asynchronously).

hitType: exception

ga('send', {
  'hitType': 'exception',
  'exDescription': 'DatabaseError',
  'exFatal': false

The final one for now, this hitType is one you don’t want to be sending – it’s to track the number of times your site/page crashes. Both “exDescription” and “exFatal” are optional. Typically this would be set wherever you may have error handling code (actually, I’ve no idea how you’d send it without it being within error handling code!)

Send a Hit with a callback!

ga('send', 'event', {
  'eventCategory': 'external link button',
  'eventAction': 'click',
  'hitCallback': function() {
    alert('analytics.js done sending data, now you may carry on.');

There might be a few reasons for this, it depends on your project. One pointed out in the docs is the ability to hold a user on the page after they’ve clicked an external link until we’re sure the hit has sent, although this isn’t too kind to the page loading after. An alternative to this is to use Navigator.sendBeacon() which sends data while the page is unloading, and analytics.js is already set up to do it! Although, we won’t be able to run a callback (after all, the page is unloading/unloaded).

ga('send', 'event', {
  'eventCategory': 'external link button',
  'eventAction': 'click',
  'useBeacon' : true,  //easy!

nonInteraction hit

ga('send', 'event', {
  'eventCategory': 'CTA Slideshow',
  'eventAction': 'change',
  'eventLabel': 'CTA2',
  'nonInteraction', true

The final property to cover on hits. When ‘nonInteraction’ is specified the hit won’t be used in calculating your bounce rate. This lets us track things that might be changing on the site automatically. In this example I’m imagining a slideshow with different CTAs and we’re tracking how often each one is seen by visitors.


That’s the basics covered! We can now create one or more trackers, modify them, switch between them, see what’s inside them, and fire them off with the various types of events. We’ve even snuck in callbacks, sendBeacon and the noninteraction hit. That should be enough to cover most of our front-end analytics needs, but if you’re looking to take it further – there’s quite a bit more! So, take a break, let this first half absorb, then we can get into the second half.

Going further with analytics.js

Here I’ll cover the rest of what’s in the docs. A lot of it won’t be required on every project but I’m sure you’ll end up playing with these things eventually. I’ve split it up into three sections. First, some additional options that are already included in analytics.js. Second, plugins which require us to require them. Third (and finally), content experiments which definitely merit a whole post, possibly even a whole book, dedicated to them – but I’ll be brave and have a shot at summing them up. And then that’s it! You’ll have reached the end of this marathon-ish article.

More analytics options

User ID: This allows us to track individual users across multiple sessions and (amazingly) multiple devices! How could this be possible?!? Well, bit of a drawback – we can only use this if your site has an authentication system for users to log into, then it will only work for users who are logged in. Ideally we could do this with everyone, but we can’t. If we could, there would probably be some breach of privacy going on somewhere. One advantage is that, alongside the in depth tracking of logged in users, this allows us to see the difference in behavior between logged in users and non-logged in users (prospective customers maybe?)

//we can add it on create
ga('create', {
  trackingId: 'UA-XXXX-Y',
  cookieDomain: 'auto',
  'userId': 'USER_ID'

//or on set
ga('set', {
  'userId': 'USER_ID'

Note, we are responsible for generating the value for USER_ID, usually it will come from the authentication system.

Custom Data: We can attach extra, project specific data to the tracker: page authors, game levels, authentication status, anything! We first have to set up the extra data type in the “Google Analytics Management Interface”, where you’ll have a choice between Dimensions (which take string data) and Metrics (which take numbers). Once set, each type will have an index number – that’s what we use on the front-end as the key value in the fields object that we add to the tracker or hit. You may have used custom variables with the old library, ga.js – these metrics and dimensions appear to be their successors, so use these instead!

ga('set', {
  'dimension5': 'string'

ga('send', 'event', {
  'eventCategory': 'category',
  'eventAction': 'action',
  'metric5': 12345

Note, free accounts are restricted to 20 extra data types, premium accounts get 200.

Anonymous IP’s: let us mask IP addresses at the beginning of the collection process and prevent their storage as this will occasionally be required by a client’s privacy policies. We can anonymize IPs on individual hits or set them on the tracker with the “anonymizeIp” field value:

ga('set', {
  'anonymizeIp': true

ga('send', 'pageview', {
  'anonymizeIp': true

Note, this has to be set on every page.

User Opt-out: This allows us to set if a user is included at all in any of the analytics data collection. Generally it’s set after asking the user if they want to opt out. We have to apply this on every page that has tracking and it must be set before the tracking code is called.

window['ga-disable-UA-XXXX-Y'] = true;

Plugins Section 2 of the second half – you’re nearly there!

Loading plugins: So analytics.js has a few plugins available that will extend its functionality for us, but they’re not there by default – we have to ask it to load them for us with the require function. This has to be called after your tracker has been created and before you send.

//On the default tracker
ga('create', 'UA-XXXX-Y', 'auto');
ga('require', 'pluginName'); //Require the plugin
ga('send', 'pageview');

//on any additional trackers
ga('create', 'UA-XXXX-Y', 'auto', {name: 'foo'});
ga('foo.require', 'pluginName'); //Require the plugin
ga('foo.send', 'pageview');

Note, require will block any commands from executing until it has loaded and run the plugin script. Also I’m just using the non-object format of passing params to ga to be more concise, apologies for the hypocrisy!

Enhanced Link Attribution: This lets us track a few extra things concerning link tags, for example when multiple links on a page have the same destination, or when one link might have several destinations (maybe its href is altered by JavaScript based on some user set parameters). As the reports generated have more detail they take more processing and more time to produce, so the docs suggest that we only use this feature when we really need it. For those times when we do, turn it on in your GA account. Then, we can set it up in the front-end:

ga('require', 'linkid');  //Tada!

Note, for this to work well, give all your link elements individual IDs.

Cross Domain Tracking: this is a way of tracking visitors across multiple domains (which we own). It’s a tricky one as browsers don’t allow cookies to be shared between different domains. So, we use the autolinker plugin to append an id to the end of any urls that point to our domains. That way the secondary domains can pick up the cookie ID from the incoming url and continue the tracking for a single session as opposed to logging a new visitor. Sounds easy? Nope – what about people sharing urls? To avoid sessions spreading, this plugin also provides a hashed time stamp, so we don’t have to! To use this we’ll have to exclude referrals from either site in the analytics dashboard:

//1. give a heads up to analytics.js that a visitor might be arriving from a sessions we're tracking
ga('create', 'UA-XXXXXX-X', 'auto', {'allowLinker': true});

//2. load the plugin
ga('require', 'linker');

//3. tell it which domains we're tracking across
ga('linker:autoLink', ['', '']);

Note, this example assumes we’re tracking users who go back and forth between domains. If the initial traffic is always one way then the source domain only needs 2. and 3. then the destination domain only needs 1. Once the _ga ID cookie has been synced, it’ll stay synced.

Display Features: enables some advertising features by firing off data to the DoubleClick Platform. Once enabled in your Google Analytics property. It’s very simple to set up in the Front End:

ga('require', 'displayfeatures');

Ecommerce Tracking – this is the simple version (advanced is next): This just sends details of a successful transaction to GA (usually via the thank you page). The details of the overall transaction are held in a transaction object and all items have their own individual item objects; they’re linked by the ‘id’ field in each, which are both set to the transaction ID. If your site handles transactions server side (hopefully it does), then it would seem easiest to dynamically generate the details for each object server side and print them out on the page.

//Load the plugin
ga('require', 'ecommerce');

//Define and add transaction data to the default tracker
var transaction = {
  'id': '1234',                     // Required.
  'affiliation': 'Store name',
  'revenue': '11.99',
  'shipping': '5',
  'tax': '1.29',
  'currency': 'EUR'
ga('ecommerce:addTransaction', transaction);

//Same deal for items
var item = {
  'id': '1234',                     // Required (transaction id).
  'name': 'Product name',           // Required.
  'sku': 'DD23444',
  'category': 'Product category',
  'price': '11.99',
  'quantity': '1',
  'currency': 'GBP'
ga('secondTracker.ecommerce:addItem', item);

//Send it all

Note, once sent the ecommerce data will be cleared. If we want to clear the data manually: ga('ecommerce:clear');. Also, to add/send ecommerce data with additional trackers, it’s the same as before, just prepend the tracker name: ga('name.ecommerce:add...
Finally, here’s a handy list of currency codes.

Enhanced Ecommerce Tracking – this is the advanced version!: It takes tracking the online shopping process to the next level – rather than just logging the final success, it tracks events from product impressions (even if they’re just in your peripheral vision) right the way through the checkout process and beyond to refunds. It’s the whole shabang. Now I’ll admit it took a lot of re-reading for me to get this straightened out in my head, so I would definitely recommend going through the Enhanced Ecommerce Docs as well as going through my summary here (unless you’re one of those people who can just skim through and ‘get’ it – actually no, I don’t believe you, go read it!)

There are two main types of interactions that we can track with this plugin. “Impressions”, which are just when a visitor is shown a product or promotion, and “Actions”, which occur when the user interacts with a product, promotion or anything in the checkout process. To track these things there are four different data object definitions. (these get added to the current hit object and piggyback on them when sent):

  • Impression data: Details about a product that was seen somewhere on your site. It looks at things like what page, position on the page, what version of the product and a few other things. Data is stored in an impressionFieldObject .
  • Product data: Similar to the impressionFieldObject but with extra optional attributes like coupon and quantity. This is meant for when a customer starts interacting with actual products (clicking them, putting them in the cart, etc). This data is stored in the productFieldObject.
  • Action data: (should be called transaction data) Contains details about the transaction, or the current step of a transaction, (it’ll make more sense after you look at the ‘Action Tracking’ section below). All its data is stored in the actionFieldObject.
  • Promotion data: This is to record details about promotions around your site, eg impressions and clicks. Similar idea to the data that gets collected around products but it’s a lot simpler. The promotion data gets stored in the promoFieldObject

Now that we have a rough idea of the interactions that get tracked and the various data objects that are used, we can look at each tracking scenario:

Impression tracking: peripheral view of a product.

//Load the plugin
ga('require', 'ec');

//Add details to the tracker
ga('ec:addImpression', {
  'id': 'P12345',                   // Product ID (string).
  'name': 'Android Warhol T-Shirt', // Product name (string).
  'category': 'Apparel/T-Shirts',   // Product category (string).
  'brand': 'Google',                // Product brand (string).
  'variant': 'Black',               // Product variant (string).
  'list': 'Search Results',         // Product list (string).
  'position': 1,                    // Product position (number).
  'dimension1': 'Member'            // Custom dimension (string).

Action tracking: For any kind of user interaction. We have access to a bunch of different action types (listed below) and each one has its own requirements. Fortunately they all have the same format when we’re calling them:

ga('ec:setAction', 'action name', {optional actionFieldObject});

Note, actionFieldObject, as mentioned in Action data above, is the data that (I think) should be called transaction data – at least that would make more sense to me – am I alone in this?

Action Name Requirements from the args object Optional extras to include
click `ec:addProduct` `actionFieldObject.list`
detail `ec:addProduct` And Or `ec:addImpression` `actionFieldObject.list`
add `ec:addProduct
remove `ec:addProduct`
checkout `ec:addProduct` And Or `actionFieldObject`
checkout_option `actionFieldObject.step` and `actionFieldObject.option`
purchase `ec:addProduct` & `` `actionFieldObject`
refund `` `ec:addProduct`
promo_click `ec:addPromo` & No product details

Tracking the checkout process: This is just an extension of the Action Tracking. The actionFieldObject has a step property that takes a number. This should be configured first in the GA dashboard (Admin -> view (profile) -> Ecommerce Settings) where you can name each ‘Funnel Step’ (every step in the checkout process: cart details, shipping address, card details…), then we can use the number of each in the step property. Otherwise your results will just show the numbers (not very human readable!)

Promotion Tracking: For all the banners and CTAs around your site. We can track promotion impressions and clicks. Both types are added, again before the hit is sent, with ga('ec:addPromo', {promoFieldObject});
* For Promo Impression: addPromo then send the pageload hit – no need for an action event
* For Promo Click: addPromo then set the action to promo_click. No product details here.

Impression + action data: can be sent on the same hit, unless the action is a promo_click – in that case they need to be sent on different hits: send the impression first then the promo_click.

Finally, for this pretty epic plugin, the docs have a really good run through of a complete Enhanced Ecommerce Tracking example.

Adding custom plugins: If all of that extra functionality that we’ve been through still isn’t enough for you, there’s always the option to write your own plugin.

//Write the constructor function for your plugin
var MyPlugin = function(tracker, config) {

//Register your plugin
ga('provide', 'myplugin', MyPlugin);

//Call your plugin
ga('require', 'myplugin', {optional config object});

Note, if a config object is passed by ‘require’ it will be passed as the second arg to your function, the first being the default / named tracker.

If you wish to have your plugin register itself, the docs have an example showing how to register your GA plugin without depending on the ga object being called ‘ga’ (as it may be changed by the magic snippet).

To expose methods from your plugin (so we can call them via ga('[targetName.][pluginName:]methodName', ...); you’ll have to follow a few conventions that have been set down, but I think I’m getting a bit deep for this article so I’ll leave off there and we can get on with the third and final section (of this half of the article!):

Content Experiments

The final section! This is like A/B testing but with up to 10 variations: A/B/N testing! This can be handled server side or on the client side. As I’m looking at all this analytics fun from the front end perspective, I’ll leave the server side stuff for you to read through (but it is pretty similar, so this might still be a good idea to read).

Have your variations ready (could be as small as button styling or as big as totally different layouts) and set them up in the analytics dashboard. I believe you can set the % of your visitors who are to included in this testing, but that’s a dashboard thing. So, to the client side, here’s the outline of what you’ll be putting together:

<!-- Load the content experiments API (optionally with experiment ID) -->
&gt;script src="//"&gt;&lt;/script&gt;


  // Find out which variation to use
  var variation = cxApi.chooseVariation();

  window.onload = function(){
    //do something with variation: change buttons, switch layouts, etc

  //load the normal analytics script (the magic snippet)

  //send a hit
  ga('send', 'pageview');


Of course there’s a lot more on content experiments but this article has gone on long enough (I bet you’re glad I said that – and well done for reading this far if you have!) So, on to the end.


Hopefully I’ve covered pretty much everything you might want to use in the front-end of a normal project. At least I no longer feel guilty about a lack of know-how in this area. For me the two places that warrant a lot more reading up on are the Enhanced Ecommerce plugin (it was by far the hardest area to digest) and the Content Experiments. If you’re after more about Google Analytics as a whole, I guess that platform overview is probably a really good place to see which subject you want to dive into next!

« Prev Article
Next Article »