How to add GDPR compatible dynamic cookies consent in 20 minutes using Google Tag Manager

  • Home
  • /
  • Blog
  • /
  • How to add GDPR compatible dynamic cookies consent in 20 minutes using Google Tag Manager

TL;DR: Step by step implementation of cookies consent for WordPress websites that takes around 20 minutes and relies on Google Tag Manager and the code from Daniel Setzermann

update: June 6, 2018 (improvements to cookies bar: larger button, now suggests user accepted if user clicked on any link on page EXCEPT Privacy Policy)
update: June 4, 2018 (improvements to cookies bar)

IMPORTANT: The information you obtain in this article is not, nor is it intended to be, legal advice. We are not responsible for any changes, issues, errors you may get while following this tutorial. The sole purpose of this blog post is to share the experience with the implementation of the cookies consent.

What is GDPR? GDPR (General Data Protection Regulation) is a new regulation that became enforceable on 25 May 2018. This new regulation is created to provide more protection for personal data sharing on the web and requires website owners to make changes to Privacy Policy and the way cookies, online tracking and user data of website visitors from Europe are used by companies.

Read more:

Since WordPress 4.9.6 (released on May 17, 2018) new tools were added by WordPress team for GDPR compatibility (see Tools menu in Admin area): Data Export Tools, Data Erasure Tools, Privacy Policy Generator that generates a basic structure for your privacy policy.

Google Tag Manager is a free online service from Google that is widely used by webmasters and marketers and allows you to put a single javascript snippet into your website just once and then you may configure (in Tag Manager) various javascript snippets that will be injected into your website. It is very useful because you may work with a handy online editor, set on which pages some javascript snippet for 3rd party marketing service, email newsletter or Google Analytics should be added. It also provides versioning, debugging, javascript error checks.. So if you have a website and you are a marketer then it really saves a lot of time!

So, let’s assume you have a WordPress website and you also use Google Tag Manager for the injection of javascript snippets from Google Analytics, Adwords, email marketing systems and others.

Daniel Setzermann made things easier and created the so-called “recipe” for Google Tag Manager which is doing the following:

  • first, it checks if a user previously agreed to be tracked and if not then it shows a consent message at the bottom of the website;
  • Until visitor confirms that she agrees to be tracked, no javascript is injected by Google Tag Manager
  • Website visitor should click on Accept button or scroll the website as a confirmation action to allow javascript snippets (like ones from marketing and analytics systems) to be run;
  • Also, until that no cookies will be placed and no tracking javascript code will be loaded.
  • Once visitor agrees by doing the action then Google Tag Manager places cookies and loads javascript snippets;
  • A visitor may go to a special cookie-settings page to change preferences and turn off tracking.
  • best of all this recipe can be quickly added to existing WordPress website with Google Tag Manager account;
  • but it requires some minor adjustments to work properly.

Step by step:

1. Download the recipe from Techmarketing.guide website and this page:

https://technicalmarketing.guide/google-tag-manager/eu-cookie-consent-for-gdpr-with-google-tag-manager/

It will download cookie_consent_version2.json file to your computer that looks like this:

This .json file is a recipe that means that you may import it into your Google Tag Manager account.

Now open your Google Tag Manager account and import this file:

Go to AdminImport Container:

Then Import Container screen will appear.

  • Choose “Merge” mode
  • Choose “Overwrite conflicting tags, triggers and variables” mode
  • Click “Choose container file” and select  cookie_consent_version2.json file from your computer
  • and then finally click on the “Existing” workplace to confirm that it will import into your existing workspace:

IMPORTANT: select Merge otherwise it will erase previous settings. But as you may know, Google Tag Manager has versioning so you should be able to rollback all changes if something went wrong.

You may check for details if any of newly added settings are conflicting with existing ones.

If all is OK (if you see no errors or warnings) then click on Confirm to finally import the recipe:

Now if you open Google Tag Manager, you will see lot of new variables, tags, triggers added:

 

OK, perfect. Now let’s configure variables will contain the main domain of your website

Click on Variables on the left panel:

Then scroll down and click on the Cookie Base URL

This will open edit for Cookie Base URL variable editor:

Change “.yourdomain.com” into the domain name of your website. If your website is “www.mysite.com” then you should enter “.mysite.com” (without quotes)Don’t forget the leading dot . because it says that cookies will work for both www.mysite.com and mysite.com (without the leading www).

Click Save button to save and now click on “Tags” menu at the left and click on the Cookie Bar to edit the consent text for the message that appears to website visitors:

The edit window will appear. Click on the code editor window, select all and then replace the code with this new one:

<style>
  #cookie_bar {
    width: 100%;
    z-index: 99999;
    position: fixed;
    bottom: 0;
    background: #FFFFE0;
    border-top: 1px solid #d2d2d7;
    padding-top: 7.5px;
    padding-bottom: 7.5px;
    font-size: 12px;
    padding: 5px 5% 20px 5%;
    font-family: sans-serif;
    line-height: 24px
}


.policy_link {
  text-decoration: none;
  color: black;
  border-bottom: 3px solid #50505a;
  padding: 0px;
}
  
  .policy_settings {
  text-decoration: none;
  color: black;
  border-bottom: 3px solid #50505a;
  margin: 0px 18px;
}

.cookie_close {
  background-color: green;
  padding: 6px 150px;
  border-radius: 3px;
  font-size: 18px;
  color: white;
  cursor: pointer;
  display:inline-block;
}
</style>

<script>

jQuery(document).ready(function() {
  
var privacy_url = 'https://MY_PRIVACY_POLICY';

var cookie_bar = document.createElement('div');
cookie_bar.setAttribute('id','cookie_bar');
var container = document.querySelector('body');
container.append(cookie_bar);
  
document.getElementById("cookie_bar").innerHTML='<strong>This website uses cookies.</strong> Cookies are used to personalize content, analyze traffic, provide social media features, display ads. We also share information about your use of our site with our social media, advertising and analytics partners who may combine it with other information that you have provided to them or that they have collected from your use of their services. Please review our <a class="policy_link" href="' + privacy_url + '">Privacy Policy</a> for more details. By closing this banner or by clicking a link or continuing to browse otherwise, you consent to our cookies.&nbsp;&nbsp;<br><input id="consent_neccessary" type="checkbox" disabled="" checked="true"><label for="consent_neccessary" style="color: gray;">Neccessary &amp; Functional &nbsp;(required)</label> <input id="consent_performance" type="checkbox" checked="true"><label for="consent_performance">Performance</label> <input id="consent_targeting" type="checkbox"><label for="consent_targeting">Marketing</label> &nbsp;<span class="cookie_close">OK</span>';


// init cookies
function initCookies(buttonClicked){
  

    jQuery("#cookie_bar").fadeOut("normal", function(){
        jQuery(this).remove();
    });
  

    /* Set Performance */
    var performance_checkbox = 0;
    if (jQuery('#consent_performance').is(":checked"))
    {	
		performance_checkbox = 1;
    };
    
    /* Set Targeting */
    var targeting_checkbox = 0;
    if (jQuery('#consent_targeting').is(":checked"))
    {
      	targeting_checkbox = 1;
    };
  	
  	dataLayer.push({
  		'event': 'setCookie',
  		'attributes': {
    	'cookieName': 'cookie_consent_performance',
    	'cookieValue': Number(performance_checkbox),
    	'cookieDomain': '{{Cookie Base URL}}',
    	'cookiePath': '/',
    	'cookieExpires': 365
 	 	}
		});  
    
    dataLayer.push({
  		'event': 'setCookie',
  		'attributes': {
    	'cookieName': 'cookie_consent_targeting',
    	'cookieValue': Number(targeting_checkbox),
    	'cookieDomain': '{{Cookie Base URL}}',
    	'cookiePath': '/',
    	'cookieExpires': 365
 	 	}
		});  
  
  // we should show the bar if cookies were accepted by simply browsing or by clicking on a link
  var settingsAccepted = 0;
  if (buttonClicked)
    settingsAccepted = 1;
  
  	dataLayer.push({
  		'event': 'setCookie',
  		'attributes': {
    	'cookieName': 'cookie_consent_settings_set',
    	'cookieValue': settingsAccepted,
    	'cookieDomain': '{{Cookie Base URL}}',
    	'cookiePath': '/',
    	'cookieExpires': 365
 	 	}
		});
  
   var cookie_settings_string = 'p' + performance_checkbox.toString() + 't' + targeting_checkbox.toString();
  
  	dataLayer.push({
          'ga_cookie_settings': cookie_settings_string,
          'event': 'consent_event',
          'consent_performance': Number(performance_checkbox),
          'consent_targeting': Number(targeting_checkbox)
        });
    
  
}; // init Cookies function

  
// catching clicks on everything EXCEPT privacy policy page link so user may view it without cookies placed
jQuery('a:not(.policy_link)').on('click', function(e){
  initCookies(0); // user started to browse but we will need to show cookies bar again
});
  
// just in case we also catching click on cookie_close button
jQuery(".cookie_close").click(function() {
  initCookies(1); // user clicked the OK button so will not show it again
});
  
}); // on document ready handler

</script>

Now search for MY_PRIVACY_POLICY inside the code and replace it with the URL to the page with Privacy Policy on your website.

And then click Save to save.

Now it is time to fix some issues related to WordPress by replacing some code to another code. These issues are related to jQuery (popular javascript library) that conflicts with built-in WordPress library.

Scroll to the top and click on Variables again and select Cookie Settings:

Click on the code editor window, select all the code:

and replace it with the following code instead:


<script>

var cookie_consent_set = {{cookie_consent_settings_set}};
var cookie_consent_performance = {{cookie_consent_performance}};
var cookie_consent_targeting = {{cookie_consent_targeting}};
  

  
(function() {
  
  /* Cookie Consent Performance */
    if (cookie_consent_set != 1) {
      /* Set Default Variables */
      cookie_consent_performance = 1;
        cookie_consent_targeting = 0;      
      /* Set Default Cookie Set */
      dataLayer.push({
  'event': 'setCookie',
  'attributes': {
    'cookieName': 'cookie_consent_performance',
    'cookieValue': 1,
    'cookieDomain': '{{Cookie Base URL}}',
    'cookiePath': '/',
    'cookieExpires': 365
         }
        });
      dataLayer.push({
  'event': 'setCookie',
  'attributes': {
    'cookieName': 'cookie_consent_targeting',
    'cookieValue': 0,
    'cookieDomain': '{{Cookie Base URL}}',
    'cookiePath': '/',
    'cookieExpires': 365
         }
        });
      /* Push Default Consent Events */
dataLayer.push({
          'ga_cookie_settings': 'p' + cookie_consent_performance + 't' + cookie_consent_targeting,
          'event': 'consent_event',
          'consent_performance': 1,
          'consent_targeting': 0
        });
    } else {
dataLayer.push({
 'ga_cookie_settings': 'p' + cookie_consent_performance + 't' + cookie_consent_targeting,
          'event': 'consent_event',
          'consent_performance': cookie_consent_performance,
          'consent_targeting': cookie_consent_targeting
        });
    } 
})();

  
  /* Preset Settings Toggles Accordingly */
  
  if (cookie_consent_performance == 1) {
      jQuery('#consent_performance').prop('checked', true);
  } else if (cookie_consent_performance == 0) {
      jQuery('#consent_performance').prop('checked', false);
  }
  
  if (cookie_consent_targeting == 1) {
      jQuery('#consent_targeting').prop('checked', true);
  } else if (cookie_consent_targeting == 0) {
      jQuery('#consent_targeting').prop('checked', false);
  } 
  
  
  /* Change State of Settings */
  
  jQuery("#consent_save").click(function() {

    /* Set Performance */
    
    if (jQuery('#consent_performance').is(":checked"))
  {
  dataLayer.push({
  'event': 'setCookie',
  'attributes': {
    'cookieName': 'cookie_consent_performance',
    'cookieValue': 1,
    'cookieDomain': '{{Cookie Base URL}}',
    'cookiePath': '/',
    'cookieExpires': 365
 	}
});
  } else {
    dataLayer.push({
  'event': 'setCookie',
  'attributes': {
    'cookieName': 'cookie_consent_performance',
    'cookieValue': 0,
    'cookieDomain': '{{Cookie Base URL}}',
    'cookiePath': '/',
    'cookieExpires': 365
 	}
});
    }
    
    /* Set Targeting */
    
    if (jQuery('#consent_targeting').is(":checked"))
  {
  dataLayer.push({
  'event': 'setCookie',
  'attributes': {
    'cookieName': 'cookie_consent_targeting',
    'cookieValue': 1,
    'cookieDomain': '{{Cookie Base URL}}',
    'cookiePath': '/',
    'cookieExpires': 365
 	}
});
  } else {
    dataLayer.push({
  'event': 'setCookie',
  'attributes': {
    'cookieName': 'cookie_consent_targeting',
    'cookieValue': 0,
    'cookieDomain': '{{Cookie Base URL}}',
    'cookiePath': '/',
    'cookieExpires': 365
 	}
});
    }
  
    dataLayer.push({
  'event': 'setCookie',
  'attributes': {
    'cookieName': 'cookie_consent_settings_set',
    'cookieValue': 1,
    'cookieDomain': '{{Cookie Base URL}}',
    'cookiePath': '/',
    'cookieExpires': 365
 	}
});
    
     location.reload();
    
  });
  
  dataLayer.push({
    'event':'showCookieBar'
  });

  // send "consent_event" and setting consent_performance and consent_targeting variables in dataLayer storage using cookie_consent_.. cookies  
  dataLayer.push({
          'event': 'consent_event',
          'consent_performance': {{cookie_consent_performance}},
          'consent_targeting': {{cookie_consent_targeting}}
        });
  

</script>

And then click Save to save.

This will return to the main page.

Click again on Variables and then click on the Load JQuery tag to edit it:

Click on the code editor and select all the code inside:

and replace all the old code with this new code:

<script>
  // send JQueryLoaded event so tags can see that JQuery is loaded (optional)
  dataLayer.push({
	    event: 'jQueryLoaded'
	  });
      
  // send "consent_event" and setting consent_performance and consent_targeting variables in dataLayer storage using cookie_consent_.. cookies  
  dataLayer.push({
          'event': 'consent_event',
          'consent_performance': {{cookie_consent_performance}},
          'consent_targeting': {{cookie_consent_targeting}}
        });
  
</script>

Then click Save to save changes.

The whole thing with events works like this:

  • “JQuery Loaded” tag is fired on every page load
  • “JQuery Loaded” fires a custom event called “consent_event” and sets “consent_performance” and “consent_targeting” in the dataLayer based on cookies values
  • “consent_performance” and “consent_targeting” triggers are fired on the “consent_event” event
  • “consent_targeting and “consent_performance” fires other tags (like FB Pixel, Analytics etc)

Why can not use Page Views instead which is much simplier? The answer is that this async workflow with events allows to dynamically load tracking code or pixel code once user clicked Accept Cookies. With simple Page Views it will require to reload the page to apply changes.

Now we also need to tie “showCookieBar” event to “consent_event” custom event.
To do this go to Triggers, then click on showCookieBar

and then click on the pencil icon to edit it and change “Event name” to the “consent_event”:

And then click on Save to save.

Your are almost done!

Now you you may try to Preview your changes first by clicking Preview

or you may publish your changes (you may always rollback to the previous version thanks to Google Tag Manager) by clicking Submit button:

If you did everything OK then should see this consent bar at the bottom of your website:

Almost done but we need to add a few more things: we need to configure existing snippets to control when they are loaded or not loaded.

First, if you have Facebook Pixel, Adwords, Analytics scripts injected then you should set triggers for them so they will be injected only if web visitor allowed it.

Use consent_targeting trigger for this check and add it your FB Pixel tag:

Repeat this for other tags (Adwords, Mailchimp tracking etc) if you have any.

You should use pre-created triggers (they were imported from the recipe):

  • consent_targeting for tags with js snippets from Adwords, Facebook and similar audience targeting marketing services.
  • consent_performance for tags with js snippets from Google Analytics and similar statistics services

You may also manually create triggers or just add condition checking these variables:

“Do Not Track” contains “1” if a user didn’t allowed to track her

“consent_targeting” contains “1” if user allowed targeting her (we will see how to allow a user to configure it)

“consent_performance” contains “1” if user allowed targeting her (we will see how to allow a user to configure it)

For example, you may add checks for “Google Analytics” snippet like on the screenshot below:

As you saw, the consent text refers to a page where a user may enable or disable permissions for performance servies tracking and/or targeting services tracking.

To create such a page just create a new page with “cookies-configure” inside URL (for example, https://mysite.com/cookies-configure or https://mysite.com/privacy/set-cookies-configure) and insert the following HTML code into that page:

<input id="consent_performance" type="checkbox" /><label for="consent_performance">Performance Cookies</label>

<input id="consent_targeting" type="checkbox" /><label for="consent_targeting">Marketing (Targeting) Cookies</label>

<button style="margin-top:15px" class="button" id="consent_save">Save Settings</button>

When user visits this page Google Tag Manager will update these HTML controls with currently saved permissions from the user. You may want to refer to this page from your Privacy Policy page.

That’s all!

Troubleshooting:

  • I see no cookies consent to appear on the website
    Solution
    : check if you have updated / replace the custom code inside “Load jQuery” and “Cookies Settings” as advised below
  • The consent bar appears and I may click on it. But every time I go to another page or relading the current one it appears again.
    Solution: 
    double check if you set “Cookie Base URL” variable to the domain name of your website. It will not save cookies properly if it differs from the actual website domain name you see in your  browser.
  • It works for my website and app but when I click another page then scripts seems not to be loaded? You may probably have a web application with caching enabled. For example, this is an issue with Rails 5 web apps. In this case, you will need to create a custom event for page views – please check this tutorial for web apps with caching

 

 


prev
next