Google Tag Manager issues, reasons and solutions
Google Tag Manager can harm your business

Google Tag Manager can harm your business


Vu Nguyen

Google Tag Manager is Great to have when it works


For a long time, website marketers and developers have a love-hate relationship. The website marketers need the developers to implement tracking features, Call To Action messages,...etc to monitor website's sales performance and improve leads and sales. The website developers, however, are always busy (or act like they are busy) and always have a tendency to say No to the ever growing list of requirements from the marketers.

Google Tag Manager aka GTM (and other Tag Managers) are built to settle this love-hate relationship once and for all. To put it simply, Google Tag Manager allows marketers (and other people who do not have direct access to the code base of the website) to inject and run additional Javascript. Please note that Google Tag Manager is not the same as Google Analytics aka GA, they are 2 completely different tools and compliment each other (instead of replacing each other). If you have been using GA to logs and analyze your website traffic and e-commerce performance, you still have to use GA when you start using GTM.

Google Tag Manager consists of these 3 main parts:
  1. Tag: A snippet of code (usually JavaScript) added to a page.
  2. Triggers: Defines when and where tags are executed.
  3. Variables: Used to receive or store information to be used by tags and triggers.

While it may take some time to learn how to use GTM efficiently, it's worth it. Before Google Tag Manager, to add any single piece of Javascript to your website you have to beg your development team to add it in for you. Need to add an event tracking? Get in line behind the urgent site issues and routine maintenance. Now with GTM, you only have to beg them once to include the GTM code on the site, then from that moment on you can embed any Javascript you want via GTM. GTM takes one step further by making event tracking so simple even for non-coders.

There is a hidden, yet HUGE issue with GTM


GTM is BLOCKED by many AdBlockers by default or via manual configurations. The latest version of Opera Browser, for example, block GTM complete by default. At the matter of fact, it even blocks Google Analytics and other tracking services such as Yandex Metrica as well.
Google Tag Manager is blocked on Opera
“ GTM is blocked on Opera as seen on SimoAhava site ”

Ok, it sucks that you cannot monitor all your website visitors, depending on your site's visitors demographics you may miss up to 30% or more of the traffic statistics. Tech focused websites and websites with younger (and more educated) audience tend to suffer more. It's the visitors' right to protect their privacy and we should honor that, yet there is an even bigger issue here: your site's important functionalities will NOT work as well.

Since GTM is such a convenient way to inject code, people tend to inject all kinds of Javascript including valid and essential script such as your Chat support box. Imagine a user coming to your website with an Ad Blocker that blocks your website's Chat support, this is the side-effect of your GTM being blocked and it hurts the users' experience and lowers your conversion rate.

Know GTM limitation


Do not put mission critical scripts in GTM. Essential scripts such as Chat support should be put directly to your website template. Do not rely on GTM being always available for every visitor that visits your website.

Have backup plan


If you still want to use GTM and still want to log users activities to Google Analytics in case GTM is blocked, you should have a backup plan.

Important: All the contents below this will be a bit technical, if you are a marketer then it's time to pass this to your developer and ask him/her nicely to implement for you.

Check if GTM is loaded


One way to check if GTM is loaded is checking for the existence of the dataLayer object which is generated by GTM and is used mainly for pushing events to GTM.
// this code should be put inside documentReady callback to ensure all scripts are loaded
if ('undefined' == typeof dataLayer || 'function' != typeof dataLayer['push']) {
    // GTM is blocked, do something about it...
}

The simplest thing you can do when GTM is blocked is logging an event to GTM. Unfortunately, there is currently no way (that I know of) to send an event to GTM once it is blocked, but we can send the event directly to GA. It's a series of unfortunate events that when GTM is blocked GA is also blocked. Lucky for us, however, Google does allow us to interact with GA server side, so there is always light behind the clouds after all.

Check If GA Is Loaded


If GTM is blocked, you may consider using GA directly to track events. Unfortunately, Ads Blockers that block GTM also tend to block GA (as well as other tracking scripts such as Hubspot, Yandex Metrica, and much more...). We can check to see if GA is loaded by checking the existence of the ga object: We can check to see if GA is loaded by checking the existence of the ga object:
// this code should be put inside documentReady callback to ensure all scripts are loaded
if ('undefined' == typeof ga || 'function' != typeof ga['send']) {
    // GA is blocked, do something about it...
}

Once we know that GA is blocked, we can simply ask our server to send the tracking event to GA instead. There are many ways to do that, you can certainly send an Ajax request to the server to do that. In my case, I prefer to serve a fake 1-pixel image like this:

var img = document.createElement('img');
img.setAttribute('style', 'display:none;');
// to prevent the browser from serving the cached image, we can append the current timestamp to the image src
var src = '/ut.php?ga={{ ga }}&action=pageview&image=1&block=1&ts=' + Date.now();
if (document.referrer) {
    src += '&document_referrer=' + encodeURIComponent(document.referrer);
}
img.src = src;
document.body.appendChild(img);

The Javascript code above will append an image to your current website that will look like this:

<img src="/ut.php?ts=123131321321"/> 

The idea is really straightforward since GA is not loaded on the browser we cannot use it to send the event to GA server, we will ask our server do it for us instead. Before we move to the next step: in my example code, I will use PHP but you can use whatever you want (ASP or anything you want). For the file extension, I use .php for convenience but if I wanted to I could have even used something like ut.jpeg. Below is a simplified version of ut.php

// print the blank image
header('Content-Type: image/png');
echo base64_decode('R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs');
// assuming you use composer, we need to include the autoload file
require 'vendor/autoload.php';
// we use this GA library to make things easier for us
// <a href="https://github.com/theiconic/php-ga-measurement-protocol">https://github.com/theiconic/php-ga-measurement-pr...</a>
use TheIconic\Tracking\GoogleAnalytics\Analytics;
$analytics = new Analytics();
$cid = null;
if (isset($_COOKIE['_ga'])) {
            $gaCookie = explode('.', $_COOKIE['_ga']);
            if (isset($gaCookie[2])) {
                // check if uuid
                if (preg_match(
                    '#^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$#i',
                    $gaCookie[2]
                )) {
                    // uuid set in cookie
                    $cid = $gaCookie[2];
                } elseif (isset($gaCookie[2]) && isset($gaCookie[3])) {
                    // google old client id
                    $cid = $gaCookie[2] . '.' . $gaCookie[3];
                }
            }
        }
// if we do not have the customer id yet, we can generate a new one
if (!$cid) {
        $cid = sprintf(
            '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
            // 32 bits for "time_low"
            mt_rand(0, 0xffff),
            mt_rand(0, 0xffff),
            // 16 bits for "time_mid"
            mt_rand(0, 0xffff),
            // 16 bits for "time_hi_and_version",
            // four most significant bits holds version number 4
            mt_rand(0, 0x0fff) | 0x4000,
            // 16 bits, 8 bits for "clk_seq_hi_res",
            // 8 bits for "clk_seq_low",
            // two most significant bits holds zero and one for variant DCE1.1
            mt_rand(0, 0x3fff) | 0x8000,
            // 48 bits for "node"
            mt_rand(0, 0xffff),
            mt_rand(0, 0xffff),
            mt_rand(0, 0xffff)
        );
        // here we set back this cookie for the user. We set to _ga right now but perhaps it's better to set to another custom key?
        // see <a href="https://stackoverflow.com/questions/16102436/what-are-the-values-in-ga-cookie">https://stackoverflow.com/questions/16102436/what-...</a> for more information
        setcookie("_ga", sprintf('%s.%s.%s.%s', 1, '/', $cid , time()), time() + 63113904);  /* expire in 2 years */
    }
$analytics->setProtocolVersion('1')
        ->setTrackingId($context['ga'])
        ->setClientId($cid);
// for pageView event, we may want to send the document path
// we can guess this from the HTTP_REFERER
// another solution is to pass this information directly via your query string
$parsed = parse_url($_SERVER['HTTP_REFERER']);
                $path = $parsed['path'];
                if (!empty($parsed['query'])) {
                    $path .= '?' . $parsed['query'];
                }
$analytics->setDocumentPath($path);
// we may also want to pass the document referrer of the original pageView
// you can use javascript to get this information and pass via query string
            if (isset($_GET['document_referrer'])) {
                $analytics->setDocumentReferrer($_GET['document_referrer']);
            }
            $analytics->setIpOverride(getClientIp());
            $analytics->sendPageview();
// helper function to get client ip
// note that this information is not 100% reliable
function getClientIp()
{
    return getenv('HTTP_CLIENT_IP') ?:
        getenv('HTTP_X_FORWARDED_FOR') ?:
            getenv('HTTP_X_FORWARDED') ?:
                getenv('HTTP_FORWARDED_FOR') ?:
                    getenv('HTTP_FORWARDED') ?:
                        getenv('REMOTE_ADDR');
}


Bonus


Perhaps you want to log how many times the users come to your website with GTM and GA blocked. You can also send this message:

// set the category, action, and label to what you like, you can later use these values to filter and export reports
$analytics->setEventCategory('block')
            ->setEventAction('block')
            ->setEventLabel('block')
            ->setEventValue(0)
            ->sendEvent();


Now that we have found a way to log page view event, why not take a step further? Veteran GTM users love it for the ability to easily listen to triggers and perform various things with it. Unfortunately, you cannot do any of these cool things if GTM is blocked, but perhaps you can still track important events and log them to your favorite Analytics tool such as GA?

Normally, GTM users will add event tracking code directly on GTM which is extremely convenient, but it also means that once GTM is blocked you will not be able to track ANY event. One option to get around this issue is to write a custom wrapper event listener and dispatcher that will still work when GTM and/or GA is blocked. Below is a sample piece of code to dispatch event event:


if ('undefined' != typeof dataLayer && 'function' == typeof dataLayer['push']) {
    // send with GTM
    // dataLayer requires the event key
    if (!data.hasOwnProperty('event') && data.hasOwnProperty('eventLabel')) {
        data['event'] = data['eventLabel'];
    }
    dataLayer.push(data);
} else if ('undefined' != typeof ga && 'function' == typeof ga['send']) {
    // fallback to JS GA
    if (!data.hasOwnProperty('hitType')) {
        data['hitType'] = 'event';
    }
    ga('send', data);
} else {
    // fallback to server side GA
    // use our custom GA
    var xhr;
    if (window.XMLHttpRequest) {
        // code for IE7+, Firefox, Chrome, Opera, Safari
        xhr = new XMLHttpRequest();
    } else {
        // code for IE6, IE5
        xhr = new ActiveXObject("Microsoft.XMLHTTP");
    }
    // use Server Side Tracking
    // there will be limitation such as GTM does not have support for this
    // we simply try to collect the information available to us
    xhr.open('GET', '/ut.php?action=event&context=' + encodeURIComponent(JSON.stringify(this.context)) + '&push=' + encodeURIComponent(JSON.stringify(data)) + '&ts=' + Date.now());
    xhr.send();
}



As you can see, we check if GTM is available, if yes then we dispatch event through GTM, if not we fallback to GA and then later to our server-side code. In my example, I encode the event data in a json conext string to pass to the server, so in the server code I will have to parse it then send the event (in my case, I send it to GA) like this:


if (isset($_GET['push'])) {
    $push = json_decode($_GET['push'], true);
    if (is_array($push)) {
        if (isset($push['eventCategory'])) {
            $ec = $push['eventCategory'];
        }
        if (isset($push['eventAction'])) {
            $ea = $push['eventAction'];
        }
        if (isset($push['eventLabel'])) {
            $el = $push['eventLabel'];
        }
        if (isset($push['eventValue'])) {
            $ev = $push['eventValue'];
        }
    }
}
// get event category
if (isset($_GET['event_category'])) {
    $ec = $_GET['event_category'];
}
if (isset($_GET['event_action'])) {
    $ea = $_GET['event_action'];
}
if (isset($_GET['event_label'])) {
    $el = $_GET['event_label'];
}
if (isset($_GET['event_value'])) {
    $ev = $_GET['event_value'];
}
$analytics->setEventCategory($ec)
    ->setEventAction($ea)
    ->setEventLabel($el)
    ->setEventValue($ev)
    ->sendEvent();



Obviously, you don't have to use GA, you can use Yandex Metrica or any other Analytics tool. Simply customize your server code to handle the event data the way you want.

Key takeaways


Do not rely on Google Tag Manager for mission critical tasks. Expect GTM to be blocked and all the tags that it's supposed to serve will never run. I recommend you to keep the important code outside of GTM, be nice to your developers or find a system that allows you to edit the code directly without asking your developers.

Big HINT: Do you know that Nilead uses our own Nilead Platform for all our website projects, and it does allow you to edit everything including CSS, HTML, Javascript on every page on your website without having to beg the developers to do so you? For the marketers who really really hate dealing with code, you can also utilize our Website Manage Service to have us do all these codes edits for you promptly and efficiently.

Source code


The source code of all the above code plus some BONUS will be packed and posted here soon. Subscribe to our newsletter or leave a comment (see below), or subscribe to one of our social media to get notified once they are available in the next few days.

about the author


Vu Nguyen
Vu Nguyen
Vu Nguyen is an entrepreneur, a developer, and a founder of nilead.com. While his love is in the backend website development work, his experience covers eCommerce (being both a real online store owner and a developer), Search Engine Optimization, UX Design and Content Strategy.

Since 2005, Vu Nguyen has led and directed UX design, full-stack development teams on projects large and small for corporations, start-ups, individuals and more. He was involved in every task of each project, from idea to concept and vision, prototype, detailed design, build and deployment.