This article will help you:
  • Implement the Optimizely snippet if you're using Google Tag Manager
  • Evaluate the tradeoffs of implementing Optimizely inside or outside of the TMS
  • Implement Optimizely and your analytics tags with Google Tag Manager
  • Implement Optimizely if you have pre-existing Universal Analytics tags

Are you thinking about using Google Tag Manager to load Optimizely's snippet? 

In this article, we'll walk through best practices and considerations for implementing Optimizely with Google Tag Manager, which does not support synchronous loading.

If you're using another TMS, like Tealium or Ensighten, please check out the linked article.

Load Optimizely Synchronously

If you’ve read our article on implementing Optimizely, you should be familiar with the idea that Optimizely needs to load in the <head> tag of your site, before any elements load on the page and before your analytics scripts run. This is how Optimizely loads variations and integrates with your analytics platforms.

To make sure that Optimizely loads and integrates correctly, you must make sure that Optimizely is able to finish loading before other tags on your page execute. This is known as synchronous loading (because all tags load synchronously, one after the other). There are two ways to ensure that this happens:

  • Load Optimizely in the <head> tag of your page, outside of a tag manager (recommended)
  • Use a tag manager that supports synchronous loading such as Tealium or Ensighten (but not Google Tag Manager)

Why does Optimizely need to be loaded synchronously? Because we apply visual changes to your website, Optimizely needs to load in the <head> tag, before your page elements load.

 
Important:

There are some potential pitfalls to loading Optimizely through a tag manager, especially Google Tag Manager, which doesn't support synchronous loading. These include:

  • If you partially deploy Optimizely on your site, you may end up not deploying Optimizely on some pages where you want to track goals. This will cause goals not to track.
  • There is some risk that another person in your organization who is unfamiliar with Optimizely may change your configuration and cause side effects.
  • If you load Optimizely asynchronously, your analytics integrations may not function properly.
  • If you load Optimizely asynchronously, you may see a "flicker" or "flash" effect, where the original page shows briefly before the variation loads.

This is why we typically recommend loading Optimizely outside of your tag manager, if possible.

Currently, Google Tag Manager does not support synchronous loadingTag managers that don't support synchronous loading will not be able to load Optimizely in the <head> tag, and can generally only load in the <body> (which is what leads to the "flashing" effect, though we'll show you some potential workarounds in this article). 

 
Tip:

Did you know you can use your TMS as a way to deploy data layer events in Optimizely that you might normally place on your page, like custom event goals? Trevor Fox at Swellpath provides a great walkthrough for how to use your Tag Manager to do this.

Where to load Optimizely and your tag manager on your page

When loading Optimizely and a Tag Manager on your site, we recommend loading Optimizely outside of your tag manager (especially if you're using Google Tag Manager). Place the Optimizely Snippet at the top of the <head> tag of your site, then place your Tag Manager below Optimizely, just as you would normally place your analytics tags below Optimizely.

Here is an example of how Optimizely and a tag manager might look in your site's code:

<head>
   <script type="text/javascript" src="//cdn.optimizely.com/js/12345678.js"></script>
</head>
<body>
   // Insert Tag Manager Code Here
</body>

New implementations: no pre-existing analytics integration

If you are using Optimizely and Google Tag Manager, we recommend loading Optimizely directly on your site and then deploy your other tags, including your analytics platform, via Google Tag Manager. We'll use Google Universal Analytics as an example in this article.

 
Tip:

If you don’t already have pre-existing Universal Analytics pageview tags configured in your GTM account, use the methods described in this section.

If you’re an existing Google Tag Manager user, you may already have pre-configured Universal Analytics tags that fire off pageview calls. If this is the case, then skip to the section below on Google Tag Manager and Universal Analytics.

Preferred: Load Optimizely synchronously outside of Google Tag Manager

This is the recommended approach because it's the most likely to prevent "flickering" or "flashing." Let's walk through the process.

  1. Make sure that the Optimizely snippet is added in the <head> tag of your page, outside of GTM.
     
  2. Add a Custom HTML tag in Google Tag Manager. Remember, for Tag Type, select Custom HTML Tagnot Google Analytics Tag. This will contain the Google Universal Analytics tracking code, with the Optimizely activation call included between the 'create' and 'send' calls. 

    The latest tracking code can be obtained from Google Analytics by navigating to the Admin tab, then clicking Tracking Info > Tracking Code

    Below is an example implementation:
    <script>
    //Universal Analytics Setup
          (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
    (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
    m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
    })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
    
          ga('create', 'UA-XXXX-Y', 'auto');
    
          // Optimizely Universal Analytics Integration Code
          window.optimizely = window.optimizely || [];
          window.optimizely.push("activateUniversalAnalytics");
    
    // End Optimizely Code
          ga('send', 'pageview');
    </script>
    
    
  3. Here's an example of what the above code might look like in GTM:


     
  4. In the above code, replace 'UA-54297114-1' with your actual Web Property ID from Google Analytics.
     
  5. Make sure that your tag has a firing rule set. Choose the "Page View" Event type.


     
  6. Back in your site's code, make sure that the GTM container script is added in the <body> tag of your site.

 
Note:

Why did we use the Custom HTML tag instead of the Google Analytics tag option? Usually, Custom HTML can be used to include any tag that doesn’t make visible changes to the page and doesn’t need to be fired synchronously or in a specific order. However, in this case, we needed to use it to add the Optimizely activation call into the normal Google Analytics snippet.


In this case, the Optimizely snippet has been hard-coded under the opening <head> tag. We then placed the GTM container code in the <body> tag, which ensures that Optimizely is fully loaded before it implements code in the GTM container. This allows Optimizely to execute synchronously and pass the necessary information between Optimizely and Google Analytics.

 
Tip:

Need to debug your integration? Use this Google Analytics Debugger. All you need to do is install it, turn it on, and open your console. It will then show you information about information being sent to GA.

Use Google Tag Manager to deploy both Optimizely and your analytics code

This is not the preferred solution, but if you must load both Optimizely and your analytics code within Google Tag Manager, here is a workaround that will allow you to do that. The key is that you want to force Optimizely to activate before your analytics code, otherwise the integration will not work.

  1. First, deploy the Optimizely tag: Add the Optimizely snippet to a Custom HTML tag in Google Tag Manager. Google provides a warning that custom tags should not be used for A/B testing, but if you're going to implement Optimizely from within Google Tag Manager, this is the only way to proceed.
     
  2. In the same tag, after the snippet, add a dataLayer variable that executes your analytics platform. Your tag in Google Tag Manager should look something like this:


     
  3. Still in Google Tag Manager, create a new rule called something like "All pages after Optimizely"
     
  4. Specify the following conditions with Event type "Custom Event":
    url matches Regex .*
    event equals optimizely_loaded



     
  5. Here's what your Optimizely tag should look like in GTM. Please set the 'firing priority' to 2:



     
  6. And here's what your GA tag should look like, including firing rules that ensure that GA deploys after Optimizely. Set the 'firing priority' to 1:

The solution above uses Custom HTML tags to deploy both Optimizely and Google Analytics. If you're using template tags (like 'pageview' tags) to deploy GA Classic, you'll need to follow a few extra steps.
  1. Go to your Google Analytics tag in Google Tag Manager, and navigate to More Settings > Advanced Configuration > Tracker Name.
  2. Check the Tracker Name box, but leave the text box blank.
  3. Click Save. This will allow you to pass Optimizely custom variables to your analytics platform.
  4. Please note that this solution will not currently work for Universal Analytics.
For other examples of this integration, check out these excellent walkthroughs by Tyson Kirksey at Vertical Nerve (who also owns the two above images) and John Pash at Easyart.
 
Important:

Using this method, we are able to ensure that Optimizely loads completely before UA fires in GTM, but you may still experience potential "flashing" or "flickering" because this setup uses asynchronous deployment. 

 

Other Google Tag Manager implementations

Are you looking to deploy Optimizely through GTM but hard-code your analytics code on your site?

Unfortunately, this setup will not work due to the asynchronous nature of the GTM container. In this setup, we cannot specify the order of execution of the Optimizely tag in relation to the hard-coded analytics tag; we can only specify its firing/loading completion priority in relation to other tags deployed in GTM. This will likely cause errors with your Optimizely/analytics integrations and page flashing/flickering.

Do you want to use GTM’s new firing priority feature to specify the order of execution?

As of July 1st, 2014, Google added a Tag Firing Priority option that allows "tags with higher numbers for priority [to] be fired first. Priority defaults to 0 if none is specified."

The Tag Firing Priority feature is intended for sites that have many tags and third-party scripts that use the same firing rule (for example, regex equals .*)

Unfortunately, this will not work with Optimizely because even though tag priority is set, the solution is still asynchronous, and tags will fire regardless of whether the previous tag has finished firing. In other words, this feature guarantees that Optimizely will fire in a certain order, but not load in a certain order. In the case of Optimizely and UA, Optimizely will not have finished processing before UA fires. This breaks the Optimizely/UA integration because custom dimensions will not be set properly.

Existing implementations using the Universal Analytics pageview tag: update the custom HTML tag in GTM

If you don’t already have pre-existing Universal Analytics pageview tags configured in your GTM account, the other methods described above in this article are recommended.

If you’re an existing Google Tag Manager user, you may already have pre-configured Universal Analytics tags that fire off pageview calls, which look like this in GTM:

If this is the case, then:

  • It’s not ideal to create the custom HTML tag to simply add the Optimizely integration, especially when you are using multiple “Advanced settings” in the tags.
  • Having the custom HTML tag fire in conjunction with these existing template tags would end up in possibly skewed results due to UA making multiple pageview calls.

In this case, it makes more sense to update the existing tags so that you can still filter your results with Optimizely segments. Below, we provide two recommendations on how to do this.

Integrate with Universal Analytics in Google Tag Manager using GTM’s custom JavaScript Macro

As is the case with all asynchronously loaded experiments (Conditionally activated, geo-targeted, etc. - anything where the timing of when the experiment is defined on the page may be affected), this solution will need to be adjusted to account for timing issues these sort of experiments present. 

We can continue to pass Optimizely experiment information to Google Analytics by using a custom JavaScript Macro within Google Tag Manager. This involves setup within all 3 applications:

In Google Analytics:

  • Create a Custom Dimension that will receive Optimizely experiment information. In order to integrate Optimizely with Universal Analytics and view reports based on the integration data, you must configure a Universal Analytics custom dimension first. This dimension, and the name you give it, will be used to create reports that leverage your Optimizely integration data for filtering.

In Optimizely:

  • Now that you've created a Custom Dimension within GA, in your Optimizely experiment, configure the experiment to pass its information to the corresponding Custom Dimension that you've just created. This can be done within the visual editor of your experiment and selecting Options --> Integrations

In Google Tag Manager:

  1. Create a new User-Defined variable within Google Tag Manager (you can find this option under Container > Variables). The Type should be a Custom JavaScript Variable.


     
  2. Here, we will use slot 3 as an example - this corresponds to Custom Dimension 3 as outlined in the GA and Optimizely examples above. You can replace this number with whichever index you select in your GA interface. For the variable name, use "Optimizely Variable Slot 3" and put this code (below the screenshot) within the Custom JavaScript section, then save it:


     
    function () {
          var o = window.optimizely,
              aE = o.activeExperiments,
              v = o.data.variations,
              gaKey = "",
              gaValue = "",
              rD = o.data.state.redirectExperiment;
    
          for(var i = 0; i < aE.length; i++) {
             var e = aE[i],
             vId = o.variationIdsMap[e],
             vIndex = o.variationMap[e],
             isMvt = vId.length > 1,
             eName = o.data.experiments[e].name;
    
             gaKey = (isMvt ? "MVT" : "AB") + " Test: " + eName + "";
             if (!isMvt) {
                 gaValue = (vIndex === 0 ? "Control" : "V") + vIndex + ": " + o.variationNamesMap[e];
             } else {
                 var s = o.data.sections,
                     sId = o.data.experiments[e].section_ids;
                 for (i = 0; i < sId.length; i++) {
                     gaValue = s[sId[i]].name.toString() + ": " + v[vId[i]].name; 
                 }
             }
             if (gaKey.length !== 0 && gaValue.length !== 0 && o.allExperiments[e].universal_analytics && o.allExperiments[e].universal_analytics.slot == 3) {
               return gaKey+": "+gaValue;
             }
         }
         
         if (rD !== undefined) {
             var rDe = rD.experimentId,
                 rvId = o.variationIdsMap[rDe],
                 rVIndex = o.variationMap[rDe],
                 rIsMvt = rvId.length > 1,
                 rEName = o.data.experiments[rDe].name,
                 rGaKey = "",
                 rGaValue = "";
    
             rGaKey = (rIsMvt ? "MVT Redirect" : "AB Redirect") + " Test: " + rEName + "";
    
             if (!rIsMvt) {
                 rGaValue = (rVIndex === 0 ? "Control" : "V") + rVIndex + " Redirect: " + o.variationNamesMap[rDe];
             } else {
                 var sR = o.data.sections,
                     sIdR = o.data.experiments[rDe].section_ids;
                for(i = 0; i < sIdR.length; i++) {
                     rGaValueArray = sR[sIdR[i]].name.toString() + "Redirect: " + v[rvId[i]].name; 
                 }
             }
             if (rGaKey.length !== 0 && rGaValue.length !== 0 && o.allExperiments[rDe].universal_analytics && o.allExperiments[rDe].universal_analytics.slot == 3) {
               return rGaKey+": "+rGaValue;
             }
         }
      }

    Modify the sample JavaScript above to match the custom dimension slot.

  3. This code above will return the experiment name and variation name key/value pair within Google Tag Manager.

  4. Within an existing pageview tag in Google Tag Manager, navigate to More Settings > Custom Dimensions. Specify the open slot that you'd like to use for your Optimizely experiment. In the Dimension field, choose Optimizely Variable Slot 3.
    Google Tag Manager Custom Javascript Variable


    Under Configure Tag --> More Settings --> Advanced Configuration --> Check the "Tracker name" box and leave the name field blank.


     
     
    Note: If you're specifying a tracker name, make sure the name in the field matches.
     
  5. Finally, in the custom report within Google Analytics for slot 3, don’t filter by anything specific; just use a wildcard RegEx (regular expression) match .*.
 
Tip:

The above guide assumes that you've already installed Optimizely natively in the <head> tag, and it’s firing synchronously. Also, here we choose slot 3 as an example. If you'd like to use other UA slots, change the naming and most importantly change the slot number within the "if" logic of the code provided as well as your Custom Dimension and report definitions.

Preserving referral sources during redirects

When a redirect takes place in Optimizely with the UA integration enabled, we currently grab the document.referrer value and call the ga('set','referrer'); function in order to maintain the original referrer. This works great on landing pages; however if there is a redirect on any page deeper into your site this strips the visitor's original session referrer and makes it the immediately preceding page. This will inflate your UA and AdWords reports' Referral traffic source.

Implement this code linked on Github so that it runs on every page load above the Optimizely snippet. On the initial landing page, this code will grab the session's initial referrer and set it to a cookie. This cookie persists through the user's navigation on your site. When the user is redirected on a non-landing page as part of an Optimizely experiment, this code will determine if the existing session's referrer should be preserved, or if it should be updated to document.referrer. Then the code sends the appropriate referrer to UA. As a result, the original traffic source is persistent.

Please be sure to note the implementation instructions at the top of the code.