Enhanced Ecommerce in Shopify. Part two: Custom dimensions and metrics

Maxim Dyuzhinov
12 min readMar 2, 2021
Photo by Randy Laybourne on Unsplash

Welcome to the second part of my micro-series about the default Enhanced Ecommerce implementation in Shopify. In the previous article we had a quick glance at what Enhanced Ecommerce data is and how Shopify collects it. This time we will go a step further and swap our comfortable suit of a passive observer for the white mask of an ethical hacker. Hacking is probably too strong a word for what we are going to do, but for the fun of it why not. Who has not dreamed of being a bad ass hacker? Here is your chance to take a look at how it’s done. We will mess with the code a little and I hope we won’t crash anything. Also I should warn you that all that you are doing, you are doing at your own risk.

Your mission, should you choose to accept it, is to add a custom dimension to the Enhanced Ecommerce product data object. A powerful international player named Shopify provides an integration with the most popular analytics service in the world, Google Analytics. This integration is 100% free and supports Enhanced Ecommerce data tracking format. We have reasons to assume that Shopify deliberately concealed some features of this integration in order to compromise our communication channel with our undercover agent in Google. You must analyze all the scripts responsible for this integration and find a way to send encrypted keys as a custom Enhanced Ecommerce product dimension. For the safety reasons each key is unique for a given product and is stored as a metafield named “wake_up_elon”.

As always, should you or any member of your team be caught or exposed, we will disavow any knowledge of your actions.

This message won’t be automatically destroyed for apparent reasons but you’ll forget it anyway when you close this page.

Reconnaissance

Reconnaissance is a beautiful word that describes a tedious and sometimes rather tiresome preparatory work in any mission or operation before going out with guns blazing. Even more, it may be a mission by itself. So technically you may do a reconnaissance for the reconnaissance mission. That is nuts, but we won’t go that far. A simple reconnaissance will do. The result of this phase is to pinpoint the element that we can exploit to achieve our main goal. In other words we will define an attack vector on the system.

What are custom metrics and dimensions?

It’s a project specific data that you may send to GA and analyze in combination with other data collected by GA automatically. For example, you can divide your customers into several categories, then send user’s category as a custom dimension and then create a sale report for each category. With custom metrics you are no longer bound to standard GA reports. You may stand out, get more insights, find an answer to your particular business problem.

When you start to setup all these new metrics you may get confused in no time. It’s OK as there is a lot of new information to deal with. Confidence comes with practice. This google tutorial is an excellent reference point to turn to in case of any questions or problems.

Both Ecommerce and Enhanced Ecommerce formats are mainly focused on product data. In GA, a product is represented by a productFieldObject with a predefined list of properties/values. Before sending product’s information to GA we should add it to the tracker.

Product data collection flow

We do it with help of the ec:addProduct command. Every time we call it the productFieldObject is being updated on the tracker. If a product object already exists on the tracker, it is merged with the new object passed by the add product command. The last transmitted value overwrites a previous one. So we can update the product object in one go or do it gradually with multiple command calls.

Single command call VS multiple command calls

The code snippet on the left produces the same result as the code snippet on the right. Also you may have noticed that we are adding here our first custom dimension ‘dimension1’: ‘Member’ as a part of the productFieldObject. The number in the name of the field is a dimension’s index. We can create up to 200 different dimension and 200 metric fields but only 20 fields of each type for free.

The second important element is the action data. It sets a context for all the data stored on the tracker. In other words it tells GA the circumstances in which the data was collected. The action data is set with help of the ec:setAction command. This command takes two parameters:

  • action type (required)
  • actionFieldObject (optional)

As soon as we have set action and product data on the tracker, we are ready to send our information to the GA server. We are ready to send a hit. I prefer to think of a hit as a data package that contains a snapshot of the data on the GA tracker, plus some additional metadata (hit type + type related data) that helps GA to process and show the data in the GA reports.

The send command sends a hit (all the data on the tracker + metadata) to the GA server. In its turn GA server receives the data and starts to process it. Later, this data becomes available in our GA reports.

What observations can we make here in the context of our mission’s objective?

  • it’s important to send custom metrics and dimensions as a part of the hit we send a product data with. This requirement demands that we add custom product metrics to the GA tracker before the send command call;
  • Enhanced Ecommerce sends product data using hits of two types: pageview and event. The event hit may be generated asynchronously, thus we should be able to add custom metrics asynchronously as well.

Trekkie library

A survival hint. JS world is a vast and dangerous place. Amateurs do not stay here for a long time, they are either being tortured for hours with an undefined variable value or have a nervous break down due to an unexpected runtime error. If we want to survive here, we must always keep in mind two things: the code execution order and the place where this code was defined.

Let’s see where Shopify defines the code for a Google Analytics tracker and how it executes it. As we already know from my previous article, Shopify uses a monorail tracking library named “trekkie”. It manages all kinds of natively supported trackers including GA. This library loads GA scripts from Google servers, initializes GA tracker, adds some data to the tracker, and finally sends this data to Google.

Shopify adds a boot script to the page header. It loads the library itself and initializes all third-party trackers. You may see that boot script below. I removed all irrelevant code from it and added a label for each significant step. I will use this snippet as a roadmap. Moving from one step to another we will analyse what is happening there, trying to figure out how it may help us to complete the mission.

The roadmap

Step #1

We load trekkie library and initialize all enabled third-party trackers. The function in the snippet below is being used to initialize a GA tracker.

Lines: 664–674

As we can see here, right at the top of the init function we start to load GA library and at the same time we create a GA tracker that we will use to send all our data to GA servers. You might have noticed that we create the tracker before GA library is fully loaded. It’s totally fine because we pass our GA commands to the global GA function named ‘ga’. If this function does not exist, we can create it ourselves. This function works like a task queue or a task FIFO buffer if you like. FIFO = First In First Out. Whenever GA library is loaded, it reads all commands from this queue and executes them in the same order they have been added there.

lines: 675–687

What observations can we make so far? The first thing that caught my attention is the window.GoogleAnalyticsObject variable. It looks like Shopify always uses a default name “ga” for the GA global function. The second thing and the most promising one: Shopify creates a new GA global function only if it has not been defined. Otherwise it uses the existing one.

It’s time to open our hacker notebook and make some notes.

Shopify uses a default name for the GA global function and does not redefine the function itself.

We can exploit it. Nothing stops us from creating a custom-made GA global function beforehand and then pass it to the trekkie library.

Step #2

At this point the trekkie library is fully loaded and GA tracker is ready to accept the data. If we add some GA events after step #2, they will be successfully sent to GA servers.

Step #3

When we use the default GA integration, Shopify gives us a very cool bonus, i.e. the ability to run a custom js code on every page including checkout pages. So technically, if you want to modify something on the checkout page, you may not need to upgrade your account to Shopify+. In the back office there is a special field named “Additional Google Analytics JavaScript” where you may add your code.

The code from this field will be executed inside the “merchantGoogleAnalytics” function as soon as the GA tracker is ready to accept the data.

It is the perfect solution to add custom metrics globally. When I say globally I mean that the custom metrics will be sent to GA servers with all the hits on the given page. All we need to do is to add few lines of code:

window.ga('set', {
'dimension1': 'custom dimension data',
'metric1': 'custom metric data'
});

Unfortunately that won’t work if we need to target one specific hit type like “event” or “pageview” or if there is a very restrictive scope for the metric, “product” for example. Hence, we should abandon this approach and keep looking for another solution.

Step #4

I’m not sure why there are two almost identical calls to the trekkie’s track function. Shopify probably treats third party events separately from its own internal stats. Who knows, but I’m pretty sure that at this point we pass over the control to the ‘viewedProductEnhanced’ function along with the data that should be added to the GA tracker.

Lines: 614–621

To get a better picture of what is going on here, I’ve added comments with a definition for each Enhanced Ecommerce function. As we can see, the ‘viewedProductEnhanced’ function does four things:

  • activates Enhanced Ecommerce plugin;
  • adds product’s data to the tracker;
  • sends tracker’s data to GA;
  • passes information about this event to the Shopify’s monorail library.

So this function adds the data to the tracker and then sends it to GA right away. Unfortunately, there is no window for us where we could squeeze in a few extra calls to GA global function to modify the product data object on the tracker. Even if we manage to modify the object e that function takes as an input parameter, that gives us nothing, as we use only predefined properties from this object. All new properties will be ignored. That leaves us with a new record in our hacker notebook.

Try to add custom product data before the first call to the Enhanced Ecommerce event function.

Step #5

In this section Shopify handles all asynchronous “add to cart” events by overwriting native ajax functions. Yep, it replaces native APIs with a custom implementation that allows to inspect a content of all ajax requests on our site. We know that ajax calls happen at runtime, that means that we can’t know for sure what product will be added to the cart, so we can’t alter product’s data on the GA tracker beforehand. We have to do it at runtime too.

To handle async "add to cart" events we should modify the product data object at runtime

So let’s sum up what we have in our hacker notebook so far and let our grey cells do their job.

Reconnaissance summary

Shopify uses a default name for the GA global function and does not redefine the function itself.
---
window.ga('set', {
'dimension1': 'custom dimension data',
'metric1': 'custom metric data'
});
---
Try to add custom product data before the first call to the Enhanced Ecommerce event function.
---
To handle async "add to cart" events we should modify the product data object at runtime.

Having read these notes and given them a little thought we may come to the following conclusion. Whenever we add custom metrics to our Enhanced Ecommerce data, there are two possible solutions.

Solutions

#1 Send global custom metrics

We already know how to do it. Just add global metrics at the end of the custom js code.

#2 Send metrics with a product scope

If we configured our metrics to have a product scope, we must send them with a hit that contains a product data object. Also we should consider the case when there are multiple products on a page and we may send product data more than once. For example, first time with a “pageview” hit and then with two ajax “add to cart” events. So this solution should cover all these cases.

As we know, Shopify uses the global GA function that exists at the moment the trekkie library starts to initialize. Thus, if we create a custom version of this function before the trekkie’s bootscript gets in control, we can modify any GA data at runtime. This new function will work as a proxy for the global GA function. It watches input parameters and if they meet some condition, it modifies them.

Finally! We have been waiting for this so long. Let’s write some code and bend some rules. Though I should warn you that Shopify has already done the biggest part of the work for us. We will write our solution based on the function that Shopify uses to replace native ajax API functions. One great composer said:

A good composer does not imitate, he steals.

I think we should take this advice and thank Shopify for help :)

First of all, we create a new theme snippet ga-custom.liquid.

This js code should be executed prior to the trekkie library. A gaCustomValues variable contains a custom dimension that we will add to the product object. All parameters that we pass to the global GA function go through our cbFilter(ar) function. The function checks if there is a ec:addProduct parameter among the arguments. If we have a match, we take the next argument in line, which should be a product object, and extend it with our custom dimension. After that, the updated list of arguments is passed to the actual global GA function which updates product data on the GA tracker.

The final touch to make all this magic happen is to link our snippet to the theme.liquid file.

...{% render 'ga-custom' %}{{ content_for_header }}...

That’s it. This solution will work, so our undercover agent in Google is back in the game and can use our secret communication channel again.

Congratulations*

*Of cause everything is not so simple and there are some limitations. This solution works on all product pages for one given product only.

In my defence I should say that theoretically it’s possible to make it work in all cases. We can create a js script that will fetch the payload for our custom fields via GraphQL API for any given product. And to run this script on every page, including checkout area, we can add it to the additional GA script field as we did for the global custom metrics (solution #1). But after all these manipulations I really doubt that we would get a production-ready solution.

If you want to use custom metrics and dimensions in your project, default GA integration is not the best option. I recommend you to upgrade your plan to Shopify+ and install Google Tag Manager. Yep, it requires some investments, but in the end, you can be sure that everything works as you expected.

Thank you for reading. Have a good time and if someone asks you — you are not a hacker.

--

--