How to honor Do-Not-Track and Global Privacy Control requests

OVERVIEW

Google documents methods to opt-out a user from Google Analytics and tracking on your web site. However, they do not show you how implement this only for users that have the Do-Not-Track or Global Privacy Control setting enabled in their web browser. This article provides two examples that perform user opt-out for Google Analytics. The techniques are applicable to other analytics and tracking code as well.

THE SOLUTION

Step One: Google’s User Opt-Out Documentation

Google’s documentation for the GA4 user opt-out and Universal Analytics user opt-out are the same.  Create a DOM window object property, with a name that starts with ga-disable- and ends with your analytics ID, then set its value to true. For example, to perform a Google Analytics, version GA4, user opt-out, add code in the following format:

window['ga-disable-G-XXXXXXXXXX'] = true;

Replace G-XXXXXXXXXX with your GA4 ID.

If you are using Universal Analytics, the user opt-out code has the following format:

window['ga-disable-UA-XXXXXXX-Y'] = true;

Replace UA-XXXXXXX-Y with your Universal Analytics ID.

If you are using both GA4 and Universal Analytics, you can include both. But be sure to use the GA4 tracking script.

Step Two: Is Do-Not-Track Enabled?

Now we need to figure out if the user has Do-Not-Track enabled. Unfortunately, DNT is a dead standard proposal and web browsers implemented the proposed standard differently.  The following javascript code should cover most implementations:

/* Test if Do-Not-Track is enabled. */
var dnt = ((window.doNotTrack && window.doNotTrack == '1') ||
           (navigator.doNotTrack && (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1')) ||
           (navigator.msDoNotTrack && navigator.msDoNotTrack == '1') ||
           (window.external && ('msTrackingProtectionEnabled' in window.external) && window.external.msTrackingProtectionEnabled()));

Step Three: Is Global Privacy Control Enabled?

At the time this article was written, browsers have not implemented the proposed navigator.globalPrivacyControl property in javascript. Plugins are available that will send the Sec-GPC: 1 request header. But there is no way for javascript to know what headers were sent during an HTML request.

We need a way for the web server to let the javascript on the web page know whether it received the Sec-GPC: 1 request header. To do this, we need a server-side script to print the Sec-GPC request header value into the inline javascript of the HTML response.  The following shows an example of how you can use PHP to pass GPC header information into your javascript…

/* Test if Do-Not-Track or Global Privacy Control is enabled. */
var gpc = <?php echo isset( $_SERVER[ 'HTTP_SEC_GPC' ] ) ? 'true' : 'false'; ?>;
var dnt = ((navigator.globalPrivacyControl || gpc) || 
           (window.doNotTrack && window.doNotTrack == '1') ||
           (navigator.doNotTrack && (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1')) ||
           (navigator.msDoNotTrack && navigator.msDoNotTrack == '1') ||
           (window.external && ('msTrackingProtectionEnabled' in window.external) && window.external.msTrackingProtectionEnabled()));

Step Four: Put it all together

Next, we need to combine the Google user opt-out code with the Do-Not-Track and Global Privacy Control detection code. The next section will show two examples of how to do this.

Notes:

  • At the time this article was written, Global Privacy Control is so new that the proposed navigator.globalPrivacyControl property is not available in web browsers. However, PrivacyBadger and other plugins can pass the Sec-GPC: 1 request header. These plugins do not add an implementation of navigator.globalPrivacyControl to your browser.
  • Web browsers implementations change so quickly that window.external has already lost support. But keeping it in the code won’t cause an error if you want to support older software.
  • Although many web browsers still have a Do-Not-Track settings, the Internet RFC Draft for Do-Not-Track expired in September of 2011.
  • A newer standard called Global Privacy Control was proposed in 2020. This proposed standard has the backing of the California state government and the European Union.  Supporters of Global Privacy Control believe that this legal backing will help the proposed standard be accepted and enforced. However, the internet is world-wide, and governments are not. There is little chance that government entities will enforce the standard, or even have legal jurisdiction to do so.
  • You could still win points with privacy concerned customers if you honor their Do-Not-Track requests.
  • Caching can cause issues with printing the Sec-GPC value into a script. Content cached by servers, CDNs, or proxies for one user session could effect later requests from other users.  Users that request privacy could effect those that don’t request privacy — and visa-versa. An good cache busting method is needed to prevent this. Or, since GPC is not fully implemented in most web browsers, you may chose to leave Sec-GPC detection out of your code.

EXAMPLES ONE, THE GOOGLE WAY

This method is the way Google intended developers to implement a user opt-out. With this method, the tracking code provided by Google has two additions.  First, it detects if there is a user opt-out request.  Then it sets the DOM window object properties to do the opt-out. The following code would be placed in the <head> section of every page on your web site….

<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX">
<script>
    /* Test if Do-Not-Track or Global Privacy Control is enabled. */
    var gpc = <?php echo isset( $_SERVER[ 'HTTP_SEC_GPC' ] ) ? 'true' : 'false'; ?>;
    var dnt = ((navigator.globalPrivacyControl || gpc) || 
               (window.doNotTrack && window.doNotTrack == '1') ||
               (navigator.doNotTrack && (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1')) ||
               (navigator.msDoNotTrack && navigator.msDoNotTrack == '1') ||
               (window.external && ('msTrackingProtectionEnabled' in window.external) && window.external.msTrackingProtectionEnabled()));

    /* The Google way of disabling tracking. Do you trust it? */
    if (dnt) {
        /* Disable tracking for GA4 and Universal Analytics */
        window['ga-disable-G-XXXXXXXXXX'] = true;
        window['ga-disable-UA-XXXXXXX-Y'] = true;
    }

    window.dataLayer = window.dataLayer || [];
    function gtag() {
        dataLayer.push( arguments );
    }
    gtag('js', new Date());

    /* Configure both GA4 and Universal Analytics */
    gtag('config', 'G-XXXXXXXXXX');
    gtag('config', 'UA-XXXXXXX-Y', {'user_id': '123456789'});
</script>

You may notice something fishy here. The Google Way still downloads the Google Analytics script, still makes gtag() calls, and still sends data via the dataLayer object. Google supposedly does nothing with this data after they receive it. Do you believe this?

EXAMPLE TWO, DO NOT TRACK MEANS NO.

This method detects a user opt-out request before loading the Google Analytics script.  If no user opt-out is detected, the Google Analytics script is loaded dynamically.  However, if a user opt-out is detected, the Google Analytics script is not loaded, and calls to the gtag() function still work, but do nothing.  This way, Goggle receives no analytics data; not even information gleaned from a download of the Google Analytics script.

<script>
    /* Test if Do-Not-Track or Global Privacy Control is enabled. */
    var gpc = <?php echo isset( $_SERVER[ 'HTTP_SEC_GPC' ] ) ? 'true' : 'false'; ?>;
    var dnt = ((navigator.globalPrivacyControl || gpc) || 
               (window.doNotTrack && window.doNotTrack == '1') ||
               (navigator.doNotTrack && (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1')) ||
               (navigator.msDoNotTrack && navigator.msDoNotTrack == '1') ||
               (window.external && ('msTrackingProtectionEnabled' in window.external) && window.external.msTrackingProtectionEnabled()));

    /* Load analytics script only if no user opt-out enabled */
    if(!dnt) {
        var docHead = document.head;
        var scriptEl = document.createElement('script');
        scriptEl.src = 'https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX';
        docHead.appendChild(scriptEl);
    }

    window.dataLayer = window.dataLayer || [];

    /* Modified gtag() function */
    function gtag() {
        /* Do nothing if Do-Not-Track is enabled */
        if(dnt) return;

        dataLayer.push( arguments );
    }
    gtag('js', new Date());

    /* Configure both GA4 and Universal Analytics */
    gtag('config', 'G-XXXXXXXXXX');
    gtag('config', 'UA-XXXXXXX-Y', {'user_id': '123456789'});
</script>

Notes:

  • Observant readers may have noticed that the dynamic script loading code does not set the async attribute like the original Google script tag. This is because all dynamically loaded javascripts have async enabled by default.
  • This only stops Google Analytics from receiving data from users that opt-out. There may still be tracking from Google Ads, or from other tracking scripts that you use on your web site.
  • You can use this technique of opt-out detection and dynamic script loading for other tracking scripts, such as Facebook, Twitter, etc.

LET YOUR CUSTOMERS KNOW

The proposed Global Privacy Control standard also provides a method of advertising your compliance to the world. First, create a folder at the root of your web site named .well-known.  This folder may already exist since it is often used in SSL certificate installation. Create a new file in this folder named gpc.json and add the following content to that file…

{
  "gpc": true,
  "version": 1,
  "lastUpdate": "2021-08-30"
}

Privacy plugins, and eventually web browsers, can now check if your web site has Global Privacy Control.

However, this is a weakness in the proposed standard. There is no guarantee that a web site with “gpc”: true in a file named /.well-known/gpc.json will honor user opt-out requests. Bad actors could claim they support GPC, but collect data anyway.