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 Do-Not-Track enabled 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.

DO-NOT-TRACK METHODS AND DETAILS

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 javascript has no access to 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. An easy way to do this is to send a cookie back to the user if the Sec-GPC: 1 request header was received.

There are two ways to do this. Adding the following to the .htaccess file in your web site’s root folder will create a cookie every time a Sec-GPC: 1 request header is received…

# Set Cookie if SET-GPC is present
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteCond %{HTTP:Sec-GPC} !^$
RewriteRule ^ -  [CO=gpc:1:%{HTTP_HOST}:0:/:secure:0:samesite]
</IfModule>

Or, if you don’t have access to .htaccess files, you can add the following to your WordPress theme’s functions.php file to set a cookie every time a Sec-GPC: 1 request header is received…

/**
 * Create Global Privacy Control cookie, if SEC_GPC exists
 */
add_action( 'init', function () {
	if ( isset( $_SERVER[ 'HTTP_SEC_GPC' ] ) && $_SERVER[ 'HTTP_SEC_GPC' ] == '1' ) {
		$domain = parse_url( get_site_url(), PHP_URL_HOST );
		setcookie( 'gpc', '1', 0, '/', $domain, true, false );
	}
} );

Now javascript can read the response cookie to see if the server received the Sec-GPC: 1 request header…

/* Search response cookie for Global Privacy Control value. */
var gpc = (-1 !== document.cookie.search( 'gpc=1;' ));

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. So javascript currently has no way to detect the web browser’s GPC setting.  However, PrivacyBadger, and other plugins, can send a Sec-GPC: 1 request header to web servers. Setting up your web server to send a response cookie with the GPC value allows client-side javascript to discover whether a browser plugin sent the Sec-GPC request header to the web server.
  • Web browser implementations change so quickly that window.external shown in the DNT javascript 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 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 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 have enough widespread legal jurisdiction to do so.
  • You could still win points with privacy concerned customers if you honor their Do-Not-Track/GPC requests.

EXAMPLE ONE, THE GOOGLE WAY

Google seems to imply that developers to implement user opt-out using the following method. 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 = (-1 !== document.cookie.search( 'gpc=1;' ));
    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, then load the Google Analytics script dynamically.  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 from users that opt-out.  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 = (-1 !== document.cookie.search( 'gpc=1;' ));
    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 for use by other services. 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.  Or perhaps they are simply unaware that there is a script on their site that harvests user data.