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

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

What are custom metrics and dimensions?

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

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

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

Step #3

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

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

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

#2 Send metrics with a product scope

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.

Curious developer at The Other Store