I’ve noticed that setting up eCommerce in Google Tag Manager (and now the new Enhanced ecommerce) is very difficult for many. I’m sure part of the problem is that eCommerce is for many users the moment that GTM forces you to take steps in to the developer’s domain, since it’s obvious that you’ll need to add some code to the web page.

This isn’t a tutorial on how to do eCommerce in Google Tag Manager. To get started, you’ll want to check out the official documentation. Sure, it might be inaccurate in places, and especially the translations are lacking, but dataLayer form and function is nicely described within.

Enhanced Ecommerce (UA) Developer Guide Google Analytics Ecommerce

This article has tips and answers to some frequently asked questions around eCommerce. Hopefully they will help you understand how eCommerce and Google Tag Manager work together.

The process in a nutshell

Here’s how eCommerce and Google Tag Manager conspire to get you the all-important data of your web transactions.

  1. Your eCommerce platform and/or your Content Management Solution (CMS) render the source code

  2. If the page includes transactional information, this source code must include all required details about the transaction in the dataLayer object

  3. When the tag which carries the transaction to GA fires, it will look at the dataLayer object

  4. If it finds all the required information within, it will send the transaction hit to GA

Most of the following chapters will shed more light on these points, but the key thing to remember is this:

Your eCommerce and/or CMS platform do almost all the work in tracking your transactions.

The backend solution, whether it’s your CMS or a dedicated eCommerce platform such as Magento, or even a WordPress plugin (such as WooCommerce), has to do the grunt work of populating the keys in the data layer.

With a Python-based application, the page template might look like this:

dataLayer.push({
    'transactionId' : '{{ transactionId }}',
});

With PHP, it might look like this:

dataLayer.push({
    'transactionId' : '<?php echo $transactionId ?>',
});

With QBASIC, it might look like this:

dataLayer.push({
    'transactionId' : 'PRINT T$; GOTO END'
});

OK, the QBASIC script was fictitious, I was trying to be funny. But nostalgia, right? Right?

So don’t expect GTM to come up with values for these variables magically. You will need a developer to help you here, and you will need a cooperative backend platform.

Now, sometimes you might be tempted to scrape the DOM for eCommerce information. That means that you have some JavaScript in place which retrieves all the necessary transactional data from elements on the page, such as headings, body text, even image ALT texts.

This is not the recommended approach!

There are many, excellent reasons for decoupling semantic information (information with no inherent value to what is rendered in the browser document) from the presentational layer. This is what the data layer was invented for. I strongly suggest you avoid taking the seemingly easy path of scraping on-page elements, especially with business-critical transactional tags.

Note, however, that some aspects of Enhanced eCommerce as it stands today might actually benefit from a combination of DOM scraping and dataLayer utilization. We’ll go over this later in the post.

Here are the actual tips:

  1. dataLayer before GTM

  2. Use a custom event

  3. Make sure all the data is there

  4. Don’t forget your JavaScript

  5. Enhanced eCommerce is special

And we’ll wrap it up with a neat summary.

dataLayer before GTM

This is a very common problem. The tag that carries your transaction information fires before the transaction data is in dataLayer. D’uh!

When a tag fires, it has access to the current state of all variables in the global namespace. dataLayer is one of them. If dataLayer doesn’t have the required variables on the moment that the transactional tag fires, you won’t see data from that hit in GA.

Here’s an example. This is what your normal eCommerce tag looks like:

As you can see, there are VERY few options to work with. You just set Track Type to Transaction, add a rule, and you’re good to go.

The point is that this tag will fire as soon as the container snippet is loaded, i.e. when the container snippet pushes 'event' : 'gtm.js' into dataLayer. Because the tag looks into dataLayer for the transaction details, they have to be there by the time the tag fires!

Thus, this will work:

<script>
  window.dataLayer = window.dataLayer || [];
  dataLayer.push({
    'transactionId': '1234',
    'transactionAffiliation': 'Acme Clothing',
    'transactionTotal': 38.26,
    'transactionTax': 1.29,
    'transactionShipping': 5,
    'transactionProducts': [{
        'sku': 'DD44',
        'name': 'T-Shirt',
        'category': 'Apparel',
        'price': 11.99,
        'quantity': 1
    },{
        'sku': 'AA1243544',
        'name': 'Socks',
        'category': 'Apparel',
        'price': 9.99,
        'quantity': 1
    }]
  });
</script>
<!-- Google Tag Manager -->
<noscript><iframe src="//www.googletagmanager.com/ns.html?id=GTM-P8XR"
  height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
  new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
  j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
  '//www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
  })(window,document,'script','dataLayer','GTM-P8XR');</script>
<!-- End Google Tag Manager -->

As you can see, the data is before the container snippet, so the dataLayer object is populated with all the required variables by the time the tag fires.

However, this will NOT work:

<!-- Google Tag Manager -->;
<noscript><iframe src="//www.googletagmanager.com/ns.html?id=GTM-P8XR"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'//www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-P8XR');</script>
<!-- End Google Tag Manager -->
<script>
  dataLayer.push({
    'transactionId': '1234',
    'transactionAffiliation': 'Acme Clothing',
    'transactionTotal': 38.26,
    'transactionTax': 1.29,
    'transactionShipping': 5,
    'transactionProducts': [{
        'sku': 'DD44',
        'name': 'T-Shirt',
        'category': 'Apparel',
        'price': 11.99,
        'quantity': 1
    },{
        'sku': 'AA1243544',
        'name': 'Socks',
        'category': 'Apparel',
        'price': 9.99,
        'quantity': 2
    }]
  });
</script>

This time, the transaction data is pushed after the container snippet has loaded, and there’s a very strong possibility that the data is not rendered in dataLayer when the Transaction Tag fires.

The same applies to any data you might try to move in a Custom HTML Tag. The Custom HTML Tag fires at its earliest with {{event}} equals gtm.js, because that’s the earliest possible event to fire your tags on. However, your transaction tag is ALSO firing on that event, meaning there’s a possibility that your transaction tag will fire first, and thus won’t find the transaction details in dataLayer. You can try to rectify this with Tag Priority, but in this case it would be better to look at the next tip.

Use a custom event

If you can’t push your data into dataLayer before {{event}} equals gtm.js fires, or if there’s even the tiniest possibility that the data won’t be rendered in time, you could just as well push a custom event of your own.

For example, if you’re using a Custom HTML Tag to build your eCommerce dataLayer, add an ‘event’ push in there, which you’ll then use as the firing rule for your transactional tags:

<script>
  dataLayer.push({
    ... eCommerce properties ...,
    'event' : 'transactionComplete'
  });
</script>

Then your transactional tag needs to fire on {{event}} equals transactionComplete. This way you’ll be 100 % certain that the data is in dataLayer by the time the tag fires, because the trigger event is pushed at the same times as the transactional data!

Make sure all the data is there

The developer guides tell you exactly what variables are required. In traditional eCommerce, here’s what your Transaction Tag will look for:

Transaction ID - transactionId Transaction Total - transactionTotal Product Name - name Product SKU - sku Product Price - price Product Quantity - quantity

If any of these fields are missing from dataLayer, the Transaction Tag will not get sent.

Oh, and also: transactionId and sku need to be unique. If you have multiple transactions with the same ID, or if you have many products with the same SKU, your data will be skewed.

With Enhanced eCommerce, there are many more fields to observe, so be sure to read both the Google Tag Manager Developer Guide (linked to in the beginning of this post), as well as the general guide for Enhanced eCommerce in Google Analytics.

Don’t forget your JavaScript

We’re in the developers’ domain here. Don’t forget that. Here are some things you should check first if your tags are not firing:

Each object key requires a value - If you want to omit a property from the push, either send it with an empty string, or drop it out of the push altogether. Don’t send it without a value at all:

dataLayer.push({
  'ecommerce' : {
    'purchase' : {
      'actionField' : , // WRONG! This key requires a value
      ...
});
dataLayer.push({
  'ecommerce' : {
    'purchase' : {
      'actionField' : '', // BETTER!
      ...
});

Know when to add a comma to the end of the line - If the properties are siblings of each other, that is, they have the same parent, then each line must end in a comma except the last line. Here’s how it should go:

dataLayer.push({
  'ecommerce' : { // No comma, starts a new hierarchy
    'purchase' : { // No comma, starts a new hierarchy
      'actionField' : {'list' : 'Apparel Gallery'}, // Comma, followed by a sibling
      'products' : [{ // No comma, starts a new hierarchy
        'name' : 'product1', // Comma, followed by a sibling
        'id' : '12345' // No comma, last member in current object
      },{ // Comma, since there are two objects in this 'products' Array 
        'name' : 'product2', // Comma, followed by a sibling
        'id' : '23456' // No comma, last member in current object
      }] // No comma, last member in current object
    }  // No comma, last member in current object
  }, // Comma, followed by a sibling
  'event' : 'transactionComplete' // No comma, last member in current object
});  

Respect the type requirements - Let’s first address the elephant in the room. Always, always remember to use a period as the decimal separator in a number, not a comma. When passing a numeric value, the comma can be horribly misinterpreted:

...
'price' : 33,75, // WRONG!
'quantity' : 1
...

Here, the browser interprets the first comma as the property separator, and the 75, that follows is not a syntactically valid key-value pair, and will result in an error. Here’s how it should look:

...
'price' : 33.75,
'quantity' : 1
...

Now the number is actually a number, with a period as the decimal separator.

With enhanced eCommerce, the type for price and other monetary values is confusingly Currency, which is not a JavaScript type, but just use a string with the decimal as the separator, e.g.

dataLayer.push({
  'ecommerce': {
    'currencyCode': 'EUR', // Local currency, type string
    'impressions': [
     {
       'name': 'Triblend Android T-Shirt', // Name, type string
       'id': '12345', // ID, type string
       'price': '15.25', // Price, type string
       'brand': 'Google', // Brand, type string
       'category': 'Apparel', // Category, type string
       'variant': 'Gray', // Variant, type string
       'list': 'Search Results', // List, type string
       'position': 1 // Position, type number
     }]
     ...

So remember: respect the type requirements. A number is written as just the plain number, using a period as the decimal separator: 15.35. No quotes there. A string is a combination of characters wrapped in single or double quotes: ’T-shirt’.

Enhanced eCommerce is special

I’ve alluded to Enhanced eCommerce a couple of times already, but now it’s time to tackle the beast. If you thought there was a lot of dataLayer this and JavaScript that in the previous tips, you’re going to overload with all the development effort that Enhanced eCommerce requires.

Let’s start with the obvious:

You do not use the Transaction tag type for Enhanced eCommerce.

The Transaction tag is for traditional eCommerce, and as the Enhanced eCommerce dev guide clearly states:

“Important: The Enhanced Ecommerce plug-in should not be used alongside the Ecommerce (ecommerce.js) plug-in for the same property.”

So don’t confuse the two. Enhanced eCommerce gives you a far more granular and funnel-like look at how visits progress in terms of your purchase funnel.

The thing is, with Enhanced eCommerce all hits are piggy-backed on top of existing tags, such as Pageview and Event. The key is to remember to set Enhanced eCommerce features on in the settings of your tags, AND to use dataLayer to carry the data through the tag to Google. Again, you will need to make sure that dataLayer is initialized with all the required data before the tags fire.

With Enhanced eCommerce, all the previous tips apply tenfold. That’s because there are so many more variables to appreciate, so many more type requirements to observe, so many more ways to get the data flowing to GA.

Another important thing about the new feature is that any tag which has Enhanced eCommerce enabled will only process whatever was in the most recent ‘ecommerce’ variable push to the dataLayer. So if you have, for example:

dataLayer.push({
  'ecommerce' : {
    'impressions' : ...
  }
});
dataLayer.push({
  'ecommerce' : {
    'detail' : ...
  }
});

Only the latter push will be processed by your tag which fires on {{event}} equals gtm.js. This is because the ‘ecommerce’ object is still processed with “version 1” of the data layer, where repeated pushes of the same object name are not merged. Thus each new push overwrites the previous value of the object. In any case, it’s crucial that you take this into account. What it means, in practice, is:

If you have many Enhanced eCommerce actions / items you need to send in a single tag, you must combine them in a single dataLayer.push()!

Needless to say, it will be practically impossible to implement eCommerce with the new features without involving your developers. After all, you are combining on-page actions (clicks, impressions) with the information in dataLayer. Even on the best of days, those two don’t mix well.

Here are some general tips for Enhanced eCommerce:

  • dataLayer has a maximum length of 300 items. A search page on a large web store can have hundreds and hundreds of products, especially if you utilize lazy load methods, or something similar. So try to populate dataLayer with as much information pre-load as possible, and refrain from dynamically adding data into the structure.

  • Sometimes the page elements might be dynamic. For example, you might need to account for sorting the items according to different criteria, or the same item might be in different categories, depending on what sort methods the user has chosen. In those cases you might need to scrape Page Category, for example, from the DOM, and push it into the product listing in the data model. Be sure to read this guide for information how to update existing products in GTM’s data layer.

  • Start small and scale up, if the task seems too big to comprehend. Start with tracking the shopping cart, checkout funnel, and purchases. Next plan and implement the path from product impression, to product click, to product detail view. Finally, work yourself around promotions, campaigns, and refunds, if you wish.

  • If you want to send multiple items using a single Enhanced eCommerce enabled GTM tag, you need to combine the items into a single push. Only the most recent dataLayer.push() with the ‘ecommerce’ key is processed by your Enhanced eCommerce enabled GTM tag! An even better practice is to always include an ‘event’ key in your post-container-snippet pushes, and use this ‘event’ key to fire your tag with every single ‘ecommerce’ push to dataLayer.

Summary

The trouble with eCommerce is that it’s so deep in the developers’ domain, but it involves a tool mainly directed at marketers. Naturally, this demands that communication between the technical and the non-technical stakeholders must flow naturally.

Biggest problems occur when the developers do not understand eCommerce, its purposes, or how transactional information is passed through dataLayer into the analytics platform. Also, sometimes non-developers want to tackle eCommerce on their own, because they’ve had success with the TMS thus far, and soon they’ve bitten more than they can chew.

The truth is that eCommerce requires a lot of knowledge transfer between the people who plan and the people who implement. Otherwise simple problems like failure to appreciate JavaScript type requirements, or what product details need to be represented in dataLayer, will cause errors.