Applying Mixpanel to Shopify Stores

Mixpanel is probably my favorite off the shelf analytics platform. More precisely, it is a behavioral analytics platform which means it can help you understand how your users are behaving when they are using your Website or mobile app. With very little effort you can understand how users progress through funnels that you define; what your retention rate looks like; and what kind of user cohorts you might have. In short, Mixpanel gives you the fine grained understanding of your users that you absolutely need at any stage of your business.

In this tutorial, I am going to cover step by step how to update your Shopify theme to install the Mixpanel JS library and start tracking page views, cart adds, and orders. I thought about breaking this post into two parts but when you want to instrument your Shopify store, it isn’t really something you want to do partially. You can do all your work in a Shopify development store and Mixpanel development project. When you are ready to promote it to PROD, it should be relatively painless.

Step 1 – Create a dev Mixpanel project

You can actually create as many projects as you want in Mixpanel for free. For this tutorial, either create a brand new one or reuse an exist dev one. In the project settings, under Access Keys, you will see your project’s token. We need that token to complete Step 3.

Step 2 – Setup your local dev environment

Make sure you have Theme Kit installed and you are using a Shopify development store. If you are brand new to Shopify template development, have a look at Step 1 in this earlier tutorial I wrote. It discusses this setup in more detail.

Step 3 – Add the Mixpanel project token as a theme setting

Add the following to config/settings_schema.json. This file contains a JSON array of objects representing different settings in your theme. I recommend adding the snippet below as the last element of this array. You will need to add a comma to the previous object in order for the JSON to continue to be a valid array.

  {
    "name": "Mixpanel",
    "settings": [
      {
        "type": "text",
        "id": "mixpanel_token",
        "label": "Token",
        "default": "Your Token"
      }
    ]
  }

In the admin dashboard, when you select to customize your theme, you can view your theme settings from the left panel in the editor. When you updated the array in config/settings_schema.json, you added Mixpanel as an entry in this list. Copy the Mixpanel project token from Step 1 into the Mixpanel token settings field.

Step 4 – Install the Mixpanel JS library

The Mixpanel documentation for installing their JS library provides you with a JS snippet where you have to replace on the last line YOUR TOKEN with the token from your project. We want to use our Mixpanel token setting that we created in the previous step so we are going to swap in {{settings.mixpanel_token}}. That removes the hard coded token so we can have Mixpanel tracking work for any store that is using your theme. Most commonly that is going to be different environments but if you are a theme developer, you can now support Mixpanel. The snippet should look like the following:

  <!-- start Mixpanel -->
  <script type="text/javascript">(function(c,a){if(!a.__SV){var b=window;try{var d,m,j,k=b.location,f=k.hash;d=function(a,b){return(m=a.match(RegExp(b+"=([^&]*)")))?m[1]:null};f&&d(f,"state")&&(j=JSON.parse(decodeURIComponent(d(f,"state"))),"mpeditor"===j.action&&(b.sessionStorage.setItem("_mpcehash",f),history.replaceState(j.desiredHash||"",c.title,k.pathname+k.search)))}catch(n){}var l,h;window.mixpanel=a;a._i=[];a.init=function(b,d,g){function c(b,i){var a=i.split(".");2==a.length&&(b=b[a[0]],i=a[1]);b[i]=function(){b.push([i].concat(Array.prototype.slice.call(arguments,
0)))}}var e=a;"undefined"!==typeof g?e=a[g]=[]:g="mixpanel";e.people=e.people||[];e.toString=function(b){var a="mixpanel";"mixpanel"!==g&&(a+="."+g);b||(a+=" (stub)");return a};e.people.toString=function(){return e.toString(1)+".people (stub)"};l="disable time_event track track_pageview track_links track_forms track_with_groups add_group set_group remove_group register register_once alias unregister identify name_tag set_config reset opt_in_tracking opt_out_tracking has_opted_in_tracking has_opted_out_tracking clear_opt_in_out_tracking people.set people.set_once people.unset people.increment people.append people.union people.track_charge people.clear_charges people.delete_user people.remove".split(" ");
for(h=0;h<l.length;h++)c(e,l[h]);var f="set set_once union unset remove delete".split(" ");e.get_group=function(){function a(c){b[c]=function(){call2_args=arguments;call2=[c].concat(Array.prototype.slice.call(call2_args,0));e.push([d,call2])}}for(var b={},d=["get_group"].concat(Array.prototype.slice.call(arguments,0)),c=0;c<f.length;c++)a(f[c]);return b};a._i.push([b,d,g])};a.__SV=1.2;b=c.createElement("script");b.type="text/javascript";b.async=!0;b.src="undefined"!==typeof MIXPANEL_CUSTOM_LIB_URL?
MIXPANEL_CUSTOM_LIB_URL:"file:"===c.location.protocol&&"//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js".match(/^\/\//)?"https://cdn.mxpnl.com/libs/mixpanel-2-latest.min.js":"//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js";d=c.getElementsByTagName("script")[0];d.parentNode.insertBefore(b,d)}})(document,window.mixpanel||[]);
mixpanel.init("{{settings.mixpanel_token}}");</script>
  <!-- end Mixpanel -->

Place the snippet in layout/theme.liquid inside the head tag.

Step 5 – Track page views

The first four steps focused on getting things setup and installed. Now we can start doing the fun stuff. To start tracking page views, append the following snippet to layout/theme.liquid inside the body tag:

<script type="text/javascript">
if ('undefined' !== typeof mixpanel) {
  mixpanel.track('Viewed Page');
}
</script>

Now let’s give this tracking a test drive. Start browsing around your development store. You might not have much but at least hit the homepage a couple times. Now in the Mixpanel project dashboard, go to the Analysis tab and select Live view. This particular view is only really useful for development and troubleshooting. It lets you see tracking events generated on your site or app in real time. If everything has been setup correctly, you should see Viewed Page events for your development store.

Step 6 – Identify users at sign up and login

The events that you track become a lot more valuable if you can attach them to actual user profiles. The opportunities to do that are at sign up and login. To handle those scenarios, we are going to need to leverage cookies. That is because unfortunately, Shopify is a little unpolished when it comes to detecting when users sign up or sign in. Regardless, I like to leverage js-cookie to manage cookies. Add the following snippet to layout/theme.liquid inside the head tag. This snippet also includes jQuery which I am using to simplify my code. If your theme is already using it, then only include js-cookie:

<script src="https://cdn.jsdelivr.net/npm/js-cookie@beta/dist/js.cookie.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.min.js"></script>

In templates/customers/register.liquid add the following script that will drop a cookie with the user email when the registration form is submitted:

<script>
$('#create_customer').submit(function() {
  Cookies.set('registered_email', $('#email').val());
});
</script>

We can now look for this cookie in layout/theme.liquid in the Mixpanel script tag that we added in Step 5.

  // If the user is logged in
  {% if customer %}
  // User just signed up
  if ("{{customer.email}}" === Cookies.get('registered_email')) {
    mixpanel.alias("{{customer.email}}");
    Cookies.remove('registered_email');
  } else {
    mixpanel.identify("{{customer.email}}");
  }

  mixpanel.people.set_once({ 
    "$name": "{{customer.first_name}} " + "{{customer.last_name}}", 
    "$email": "{{customer.email}}" 
  });
  {% endif %}

Step 7 – Order Processing

Shopify generates its own order processing pages which has its strengths and weaknesses. One of those drawbacks comes in having to replicate the tracking logic for those pages regardless if you are using Mixpanel or not. It isn’t the worse thing in the world but it is definitely something you need to address when instrumenting your store.

From your store’s admin dashboard, go to the settings page and click checkout. On the checkout settings page, scroll down to the order processing section. There you will see a text area called additional scripts. Copy and paste the snippet below in there. Don’t forget to replace YOUR TOKEN with your Mixpanel project token.

  <!-- start Mixpanel -->
  <script type="text/javascript">(function(c,a){if(!a.__SV){var b=window;try{var d,m,j,k=b.location,f=k.hash;d=function(a,b){return(m=a.match(RegExp(b+"=([^&]*)")))?m[1]:null};f&&d(f,"state")&&(j=JSON.parse(decodeURIComponent(d(f,"state"))),"mpeditor"===j.action&&(b.sessionStorage.setItem("_mpcehash",f),history.replaceState(j.desiredHash||"",c.title,k.pathname+k.search)))}catch(n){}var l,h;window.mixpanel=a;a._i=[];a.init=function(b,d,g){function c(b,i){var a=i.split(".");2==a.length&&(b=b[a[0]],i=a[1]);b[i]=function(){b.push([i].concat(Array.prototype.slice.call(arguments,
0)))}}var e=a;"undefined"!==typeof g?e=a[g]=[]:g="mixpanel";e.people=e.people||[];e.toString=function(b){var a="mixpanel";"mixpanel"!==g&&(a+="."+g);b||(a+=" (stub)");return a};e.people.toString=function(){return e.toString(1)+".people (stub)"};l="disable time_event track track_pageview track_links track_forms track_with_groups add_group set_group remove_group register register_once alias unregister identify name_tag set_config reset opt_in_tracking opt_out_tracking has_opted_in_tracking has_opted_out_tracking clear_opt_in_out_tracking people.set people.set_once people.unset people.increment people.append people.union people.track_charge people.clear_charges people.delete_user people.remove".split(" ");
for(h=0;h<l.length;h++)c(e,l[h]);var f="set set_once union unset remove delete".split(" ");e.get_group=function(){function a(c){b[c]=function(){call2_args=arguments;call2=[c].concat(Array.prototype.slice.call(call2_args,0));e.push([d,call2])}}for(var b={},d=["get_group"].concat(Array.prototype.slice.call(arguments,0)),c=0;c<f.length;c++)a(f[c]);return b};a._i.push([b,d,g])};a.__SV=1.2;b=c.createElement("script");b.type="text/javascript";b.async=!0;b.src="undefined"!==typeof MIXPANEL_CUSTOM_LIB_URL?
MIXPANEL_CUSTOM_LIB_URL:"file:"===c.location.protocol&&"//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js".match(/^\/\//)?"https://cdn.mxpnl.com/libs/mixpanel-2-latest.min.js":"//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js";d=c.getElementsByTagName("script")[0];d.parentNode.insertBefore(b,d)}})(document,window.mixpanel||[]);
mixpanel.init("YOUR TOKEN");</script>
  <!-- end Mixpanel -->

<script type="text/javascript">
mixpanel.track('Viewed Order Status');

mixpanel.identify("{{customer.email}}");
mixpanel.people.set_once({ 
    "$name": "{{customer.first_name}} " + "{{customer.last_name}}", 
    "$email": "{{customer.email}}" 
  });
</script>

In the snippet that I have provided, in addition to tracking the “Viewed Order Status” event, I also associate the event with the user profile identified using email. That impacts how guest checkouts are tracked since we do not have those user emails until then. It is a judgment call here since using mixpanel.identify() will orphan prior anonymous events generated by those users. In my opinion, it is worth it since your most valuable insights will come from converting customers.

To test this step, you should place some test orders. The instructions for setting up a bogus gateway in your Shopify development store are here.

Step 8 – Track orders

After Step 7, you should have all of your store page views tracked as user events in Mixpanel. At this point, you can setup funnel and retention reports in Mixpanel that will tell you how well your store is doing at converting new users into buying customers and if it is able to keep them coming back. Those insights are quite important at any stage of your store and can help you make vital UX adjustments.

In this step, we are updating our order process tracking to attribute order values to our users in Mixpanel which is helpful in defining cohorts. It might feel like we have skipped over cart adds which is an important event for our funnel reporting but the changes that we need to make are directly related to the ones you just did in Step 7. The next step looks at cart adds.

Append the line below to the last script tag in Step 7. It associates the total checkout value with the profile we have for the user in Mixpanel:

mixpanel.people.track_charge({{checkout.total_price}} / 100); 

So the additional scripts text area in your order processing section should look like the following. Take care to replace YOUR TOKEN with your Mixpanel project token:

  <!-- start Mixpanel -->
  <script type="text/javascript">(function(c,a){if(!a.__SV){var b=window;try{var d,m,j,k=b.location,f=k.hash;d=function(a,b){return(m=a.match(RegExp(b+"=([^&]*)")))?m[1]:null};f&&d(f,"state")&&(j=JSON.parse(decodeURIComponent(d(f,"state"))),"mpeditor"===j.action&&(b.sessionStorage.setItem("_mpcehash",f),history.replaceState(j.desiredHash||"",c.title,k.pathname+k.search)))}catch(n){}var l,h;window.mixpanel=a;a._i=[];a.init=function(b,d,g){function c(b,i){var a=i.split(".");2==a.length&&(b=b[a[0]],i=a[1]);b[i]=function(){b.push([i].concat(Array.prototype.slice.call(arguments,
0)))}}var e=a;"undefined"!==typeof g?e=a[g]=[]:g="mixpanel";e.people=e.people||[];e.toString=function(b){var a="mixpanel";"mixpanel"!==g&&(a+="."+g);b||(a+=" (stub)");return a};e.people.toString=function(){return e.toString(1)+".people (stub)"};l="disable time_event track track_pageview track_links track_forms track_with_groups add_group set_group remove_group register register_once alias unregister identify name_tag set_config reset opt_in_tracking opt_out_tracking has_opted_in_tracking has_opted_out_tracking clear_opt_in_out_tracking people.set people.set_once people.unset people.increment people.append people.union people.track_charge people.clear_charges people.delete_user people.remove".split(" ");
for(h=0;h<l.length;h++)c(e,l[h]);var f="set set_once union unset remove delete".split(" ");e.get_group=function(){function a(c){b[c]=function(){call2_args=arguments;call2=[c].concat(Array.prototype.slice.call(call2_args,0));e.push([d,call2])}}for(var b={},d=["get_group"].concat(Array.prototype.slice.call(arguments,0)),c=0;c<f.length;c++)a(f[c]);return b};a._i.push([b,d,g])};a.__SV=1.2;b=c.createElement("script");b.type="text/javascript";b.async=!0;b.src="undefined"!==typeof MIXPANEL_CUSTOM_LIB_URL?
MIXPANEL_CUSTOM_LIB_URL:"file:"===c.location.protocol&&"//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js".match(/^\/\//)?"https://cdn.mxpnl.com/libs/mixpanel-2-latest.min.js":"//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js";d=c.getElementsByTagName("script")[0];d.parentNode.insertBefore(b,d)}})(document,window.mixpanel||[]);
mixpanel.init("YOUR TOKEN");</script>
  <!-- end Mixpanel -->

<script type="text/javascript">
mixpanel.track('Viewed Order Status');

mixpanel.identify("{{customer.email}}");
mixpanel.people.set_once({ 
    "$name": "{{customer.first_name}} " + "{{customer.last_name}}", 
    "$email": "{{customer.email}}" 
  });

mixpanel.people.track_charge({{checkout.total_price}} / 100); 
</script>

Step 9 – Track cart adds

Tracking cart adds are a little different than page views since a lot of stores have incorporated functionality that keeps users on the page they are viewing when they add something to cart. Regardless of how your store handles cart adds, they should all involve the same form submission so the snippet below will do the trick. Append it to the Mixpanel script tag that you have been developing in layout/theme.liquid.

$('form[action="/cart/add"] [type="submit"]').click(function(){
  mixpanel.track('Cart Add');
});

Step 10 – Logout

In this final step, we handle logouts which is important for tracking users on shared computers. You don’t want to accidentally attribute events from a different user who just happen to use the same computer as another. Add the following to the Mixpanel script tag in layout/theme.liquid.

  // Reset tracking on logout
  $('a[href*="/account/logout"]').click(function(){
    mixpanel.reset();
  });

Closing thoughts

After completing the above 10 steps, the script tag in layout/theme.liquid that contains all the Mixpanel integration logic should look like the following. It will give you the basics which is a lot in terms of analytic insights that you will now be able to get out of Mixpanel.

<script type="text/javascript">
if ('undefined' !== typeof mixpanel) {
  // Capture all page views
  mixpanel.track('Viewed Page');

  // If the user is logged in
  {% if customer %}
  // User just signed up
  if ("{{customer.email}}" === Cookies.get('registered_email')) {
    mixpanel.alias("{{customer.email}}");
    Cookies.remove('registered_email');
  } else {
    mixpanel.identify("{{customer.email}}");
  }

  mixpanel.people.set_once({ 
    "$name": "{{customer.first_name}} " + "{{customer.last_name}}", 
    "$email": "{{customer.email}}" 
  });
  {% endif %}

  // Capture cart adds
  $('form[action="/cart/add"] [type="submit"]').click(function(){
    mixpanel.track('Cart Add');
  });

  // Reset tracking on logout
  $('a[href*="/account/logout"]').click(function(){
    mixpanel.reset();
  });
}
</script>

I cannot emphasize just how important it is to understand how your users progress through your various conversion funnels and what their retention behavior looks like. Without these kinds of insights, you are basically making guesses as to how to optimize your UX. Understanding how and why your users shop your store is critical to being successful.

You should get a lot of mileage out of this tutorial. There is, however, some room for optimizations and customizations:

  • All page views are treated the same: In Mixpanel, you can leverage filters to isolate different pages in your funnel analytics however, it is a good idea to create custom events for pages that you feel are important in one or more of your funnels. That improves the depth of analytics that you can get out of Mixpanel since those custom events will then be distinct from the other page views.
  • mixpanel.identify() only needs to be called after user login: It doesn’t hurt anything by call it with every page load but it isn’t necessary either.
  • Add custom properties to events, people, and charges: Mixpanel lets you decorate your events, users, and charges with any custom property you see fit. That enables you to do some very useful event filtering and user cohort analyses.
  • Cart add and logout click events assume no DOM manipulation after page load: Typically this is a safe assumption however there are Shopify apps out there that inject in cart add forms. You will need to do something a little special to handle those apps if you have them installed in your store.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: