Google Analytics: Cookieless Tracking Without GDPR Consent
This article presents a simple method to use Google Analytics without cookies. Going cookieless means there is no need for a cookie consent dialog. It also means much easier GDPR compliance. The only thing you need to do is to embed the Google Analytics tracking script in a different way, which should be possible with most web publishing platforms. Please bear in mind that my background is technical, not legal.
TL;DR:
- Remove your current Google Analytics script
- Add the cookieless Google Analytics implementation:
- WordPress: install the plugin Cookieless Privacy-Focused Google Analytics
- Other platforms: add the script to the head of your site’s HTML
Why Website Analytics Without Cookies?
The EU cookie directive (as it is often called) requires user consent before tracking cookies may be stored on the user’s device. I am a big fan of online privacy, but that is the wrong approach. It leads to the situation we have today, where every single site harasses first-time users to consent to … whatever … or else … Unfortunately, we seem to be stuck with this legislation for the time being.
High time we take back control of the web’s UX. Cookie consent dialogs need to go away. Until the legislation changes we can only do one thing: get rid of tracking cookies (which is not so bad a result, to be honest).
A Browser Extension Solution
Getting rid of cookies involves changes to a website’s code. End users cannot modify the sites they visit, of course, but they can change what their browsers do with the cookie consent harassment by installing a browser extension. I warmly recommend I don’t care about cookies. It does exactly what one would expect it to do.
How Google Analytics Uses Cookies
The HTTP protocol is stateless: there is no concept of users or sessions. Consequently, identifying users is difficult and error-prone. Google Analytics tries to solve that problem by creating unique strings that persist between page hits. These unique strings are stored in cookies.
So, basically, cookies are but a storage mechanism for unique IDs. Cookies are not the only technology that can be used to persist data in a browser. Local storage and similar techniques are available, too. All of them have in common with cookies that they require user consent, though.
Google Analytics Scripts
The algorithmic logic of generating the unique IDs required for website visitor tracking is embodied in JavaScript snippets that need to be loaded on every page webmasters want to include in their analytics. There are three generations of Google Analytics scripts currently in use that I am aware of.
ga.js
ga.js
is the oldest of the three Google Analytics scripts and now considered legacy. It can still be used (for compatibility reasons) but Google recommends one of its successors for new projects. From a privacy point of view, ga.js
is less than optimal because cookies cannot be disabled.
analytics.js
analytics.js
is the successor of ga.js
. It is more modern and, most importantly, allows cookies to be disabled.
For those planning to upgrade: here is Google’s migration guide from ga.js to analytics.js.
gtag.js
gtag.js
is the newest of the three Google Analytics scripts. It is not a successor of analytics.js
, though, but a wrapper. In short, you only need gtag.js
if you’re using other Google services in addition to Analytics (more information).
Disabling Google Analytics Cookies: Simple Solution
Disabling cookies with Google Analytics is quite simple: make sure you’re using the analytics.js
script and set storage
to none
in the create
command (details).
That works quite well, but it breaks part of Google Analytics’ functionality. Without cookies, Google Analytics’ client ID is not persisted between page views. As a consequence, every page view is considered a unique visit. This skews user and session counts significantly.
Disabling Google Analytics Cookies: Advanced Solution
An advanced solution not only disables Google Analytics cookies but also changes the way client IDs are generated for the better (from a privacy point of view).
Our Requirements
- We create the client ID for Google Analytics ourselves, taking over from Google’s script.
- We don’t want to store the ID on the client, so we must be able to recreate it on every page a user visits.
- We must not send personally identifiable information to Google Analytics.
- The client ID should be different for every website (we don’t want to create a super-ID that can be used to track users across the internet).
- The client ID should distinguish between multiple users sharing a public IP address (NAT or proxy).
How Others Are Doing It
Matomo
- Matomo visitor fingerprint
- Components: operating system, browser, browser plugins, IP address and browser language
Plausible
- Plausible’s data policy
- Components: IP address + website domain + user agent
Fathom
- Fathom’s anonymization
- Algorithm focused on privacy
Our Solution
We don’t want to send personally identifiable information to Google Analytics, so we hash the raw data that goes into the client ID. We also don’t want Google to track us over long periods of time, so we regenerate the client ID every few days.
Given those premises, we create our client ID dynamically during page load by hashing: IP address
+ website domain
+ user agent
+ language
+ validity days
.
Interestingly, the only way to get your IP address from within JavaScript in a browser is to ask a server. We use a little trick: since we have control over the code on the server, we stamp the client’s IP address into the script the server sends to the client.
The browser’s user agent is not reliable but that’s OK. We just want something that differs between browser types and, ideally, OS versions.
We consider validity days to be the number of days before the hash changes. A shorter interval improves privacy but makes session tracking more unreliable.
As hash algorithm we use cyrb53, which seems to be high-quality and fast. Caveat: our implementation does not work in Internet Explorer.
Implementing Cookieless Google Analytics
WordPress
If your site is running on WordPress head over to the plugin directory and install Cookieless Privacy-Focused Google Analytics.
Contributing to the Plugin
Contributions to the plugin’s code are welcome. Please submit your pull requests to the development repository on GitHub.
Other Platforms
If you’re not running WordPress or don’t want to install my plugin, add the following JavaScript code to your pages’ HTML head section instead:
<script>
const cyrb53 = function(str, seed = 0) {
let h1 = 0xdeadbeef ^ seed,
h2 = 0x41c6ce57 ^ seed;
for (let i = 0, ch; i < str.length; i++) {
ch = str.charCodeAt(i);
h1 = Math.imul(h1 ^ ch, 2654435761);
h2 = Math.imul(h2 ^ ch, 1597334677);
}
h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507) ^ Math.imul(h2 ^ h2 >>> 13, 3266489909);
h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507) ^ Math.imul(h1 ^ h1 >>> 13, 3266489909);
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
};
let clientIP = "{$_SERVER['REMOTE_ADDR']}";
let validityInterval = Math.round (new Date() / 1000 / 3600 / 24 / 4);
let clientIDSource = clientIP + ";" + window.location.host + ";" + navigator.userAgent + ";" + navigator.language + ";" + validityInterval;
let clientIDHashed = cyrb53(clientIDSource).toString(16);
(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', 'YOUR-GA-TRACKING-CODE', {
'storage': 'none',
'clientId': clientIDHashed
});
ga('set', 'anonymizeIp', true);
ga('send', 'pageview');
</script>
Please make sure to replace YOUR-GA-TRACKING-CODE
with your actual Google Analytics tracking code.
Also, if your web server is not running PHP, you need to find another way of embedding the client’s IP address. In that case, replace {$_SERVER['REMOTE_ADDR']}
with whatever is required by your platform.
Checking a Website’s Cookies
Don’t check cookies in your browser, the results might not be similar to what first-time visitors get. Use an online cookie checker tool instead.
Alternatives to Google Analytics
While researching this topic I found several website tracking and analytics services that focus on privacy and GDPR compliance. My notes reflect my requirements: I need to be able to track downloads, which some vendors support through custom events that can be generated with client-side JavaScript.
Matomo (formerly Piwik)
- Website
- Not to be confused with Piwik Pro
- SaaS or on-premises
- Mature product, lots of configuration options
- Can be configured not to use cookies
- Automatic download tracking (in my testing, nothing ever showed up in the Downloads report, though)
- JavaScript event support nearly syntax-compatible with Google Analytics
Fathom
- Website
- Privacy-friendly analytics
- Does not use cookies
- Single-page dashboard
- Supports events, but only one dimension (whereas GA and Matomo store three dimensions per event: category, action, and label)
Simple Analytics
- Website
- Privacy-friendly analytics
- Does not use cookies
- Event support “highly experimental”
Plausible
- Website
- Privacy-friendly analytics
- Does not use cookies
- Supports events, but only one dimension (whereas GA and Matomo store three dimensions per event: category, action, and label)
Minimal Cookieless Web Analytics
- Website
- DIY solution by Christian Bär
- Visitor identification through browser fingerprinting
- Data storage in Azure
- Visualization via a simple dashboard
47 Comments
Tolle Lösung, danke dafür. Ich hab einen Beitrag über deinen Artikel in meinem eigenen Blog verfasst und auf deine Seite verwiesen: https://www.georgkaser.com/blog/suchmaschinen/spar-dir-den-cookie-banner-google-analytics-ohne-cookies-nutzen/
Jetzt wollte ich dich fragen: Ist es für dich okay, wenn ich den Codeschnipsel selbst auch auf meiner Homepage anzeige oder lieber nicht? Möchte nicht dein Copyright verletzen ;)
Beste Grüße aus Worms!
Hi Georg, happy to hear you like the solution, and thanks for asking. Yes, please go ahead and publish the script on your blog, too. Obviously, attribution (a link to the original) would be nice, but you already did that.
I’d love to be able to use analytics without the cookies but I value pagespeed higher so it’d be perfect if the plugin could load GA locally like the one I’m currently using: https://wordpress.org/plugins/simple-google-analytics/
The Google Analytics script
analytics.js
should be in the browser cache already from other websites, so, in practice, it won’t be fetched from a webserver at all.Hi Helge,
But by calling directly the analytics.js script, browser like Safari with ITP, Firefox with ETP or Brave can block the script by seeing it calls a google domain. This plugin hosts the google analytics locally for this reason : https://fr.wordpress.org/plugins/host-analyticsjs-local/
PS: thanks a lot for your work and this post !
Even if the script is hosted locally, would not the browser block any subsequent requests to GA with actual analytics data generated by the script anyway?
Hey Helge,
Thanks a lot for the article. We’re exploring cookieless tracking options at the moment, and this is certainly attractive. What are you actually able to see in GA once this is implemented though? Uniques, page views etc.? Also, since this doesn’t work in IE, is the practical effect that those Users are not tracked at all?
Cheers,
Alex
With this solution, you see everything in Google Analytics the way you normally would. The only difference is that longer-term tracking of users does not work because the client ID is regenerated every few days (configurable through
validity days
). But that is the point, of course.Regarding Internet Explorer: the script I found for the hashing algorithm does not work in IE and I simply could not be bothered to rewrite it in such a way that it does. To make the solution work in IE you only need to replace the hashing function. As for the practical effects: take a look of the percentage of IE visitors you have today and use that as a basis for deciding whether ignoring IE is acceptable for you.
Was ich echt immer noch nicht verstanden hab: Warum zum Kuckuck verwendet man nicht einfach die Server-Logs? Dafür gibbet doch eimerweise Scripts und Statistiktools.
.. Ja, klar, JS-Events sind mit sowas eher schwer zu erfassen, aber lets face it: Für eine umfangreiche Analyse auf JS-Event-Basis, die mir tatsächlich was bringt, braucht es schon etwas mehr als nur GA, also etwa eine Heatmap. Oder A/B-Tests. Oder Tests mit “echter” Benutzerbasis.
cu, w0lf.
Hello,
We are considering this plugin at my agency for one of our top clients. However, we still foresee some data reporting issues around Client ID collisions (where two users use the same ID). Most of the time, the distinct IDs created by your plugin’s functionality should solve for that, but there may be instances where site visitors are using the same browser on the same VPN and they are assigned the same IP address (such as a corporate setting). Would not all of those users be given the same Client ID? If so, that would impact the accuracy of GA data like session durations and event flow would it not?
If these are known issues, do you provide support to allow us to merge fixes for those issues if we submitted them directly (assuming we are to proceed with the plugin)?
Thank you,
Brandon Kenig
Audience Growth Strategist
10up.com
Hi Brandon,
If client IDs are to be regenerated on the fly without storing any information on the client, collisions cannot be completely ruled out, there are simply not enough distinguishing elements available to work with. Basically, you’re paying for improved privacy with a slightly increased margin of error.
I am aware of fingerprinting techniques that try to identify a specific browser instance more accurately but consciously decided against them. After all, the point of the technique used is to not send anything to Google that could be interpreted as personally identifiable information, specifically not some kind of “super ID”. Also, browser developers seem to be working hard to make fingerprinting less and less feasible, and with good reason.
I would be happy to accept code contributions made in the “spirit” of the plugin. I just added the section Contributing to the Plugin with a link to the GitHub repo.
Very impressive. The rule many are trying to abide by is that facts/storage about the users computer should not be used and this includes cookies but also IP address / user agent. Are there any other ways to create and persist the Id that aren’t based on these factors.
Hi Helge,
Thanks so much for sharing this! I wanted to test it out on my personal WP blog, but I’m still seeing GA cookies after activating your WP plugin.
I’m probably missing something… any ideas? The blog is at https://autofire.dk/
Hello Helge,
I’ve been working with a client to bring this forward as a solution to their GDPR and consent requirements, but when I presented this to them, their response was this:
“Based on https://helgeklein.com/blog/2020/06/google-analytics-cookieless-tracking-without-gdpr-consent/, it sounds like this is doing browser fingerprinting based on IP address, user agent and language. The user agent and language (and arguably IP address) are stored on the device so I think unfortunately this means that the fingerprinting solution is not wholly responsive to our compliance requirements.”
Is this a fair assessment of your approach? I didn’t think your solution utilized browser fingerprinting based on IP address, user agent, and language and that this wasn’t stored on the device.
Thank you,
Brandon Kenig
Hi,
I don’t understand why it’s a problem to use IP address in the generated ID as the IP address will be salted and hashed. And as replied above by Helge:
“I am aware of fingerprinting techniques that try to identify a specific browser instance more accurately but consciously decided against them. After all, the point of the technique used is to not send anything to Google that could be interpreted as personally identifiable information, specifically not some kind of “super ID”. Also, browser developers seem to be working hard to make fingerprinting less and less feasible, and with good reason.”
I think the ID generated is not the most precise you could create.
For your client, it will be the same problem even if the IP address will not be in the generated ID. The problem for them is the generated ID could identify someone personnaly. But as this ID is not persistent for a long time, I think it’s good for privacy and a good solution to understand what’s happened on our websites.
(Thanks Helge)
Google has a minimum hashing requirement of SHA256 for an ID containing PII – so is cyrb53() sufficient?
https://support.google.com/analytics/answer/6366371?hl=en#hashed
Has anyone got something like this working on AMP? Using the amp-analytics widget.
I’m afraid this approach still requires user consent from what I’ve been reading (see below GDPR – Article 4, Section 1), it specifically mentions “online identifiers” and that they require legal basis to process.
https://eur-lex.europa.eu/legal-content/EN/TXT/HTML/?uri=CELEX:02016R0679-20160504&from=EN#tocId35
Good stuff. I’d like to see portals for a “cookieless web” or some kind of no-cookie community of websites that are cookie-free. Doing away with cookies is challenging and may not be possible for all websites – but has some fantastic benefits. Not only privacy compliance but massive boost to page load times. Ditching all the various scripts that were causing 10+ second load times even with a dedicated hosting package, I have brought my load times down to under 0.5 seconds. And then of course one is deliberately opting out of “surveillance capitalism”. Many webmasters are quite content to allow third parties to spy on their visitors in return for widgets that give enhanced functionality – but isn’t it time we tossed this faustian pact in the garbage can and shifted the paradigm altogether? Plausible and others are showing that it can be done. Kind regards.
Hello,
thanks for the script, I am using it on my site!
But is it possible to adapt the script so it will work with gtag.js? Because gtag.js is required for the new Google Analytics 4-Property. What’s your opinion about this?
Best regards!
Hello, this solution is absolutely amazing and works great. Thank you.
The only two issues I could think of are:
1. Returning users in GA
Setting the ‘validity days’ to 5, would make every returning user a ‘new user’ when he/she returns 6 days later.
We could set the ‘validity days’ to 30 of course and have a more accurate statistic then.
2. CMS Page Cache
It sounds like with a page cache enabled, the first cache-generating visitor would have its clientIP stored in the cached version of the page.
The clientIDHashed at the website would thus differentiate with: user agent + language.
The [IP address} + [website domain} + [validity days] would be the same for all the visitors of the same cached webpage : (
To replicate:
1. Install the code into WordPress website with page caching enabled [even when there is no caching plugin, but the website is on WPEngine hosting]
2. Open the specific webpage source in browser’s incognito-mode and find you IP hard-coded as: let clientIP = “……………”;
3. Flash website cache and run the GTMetrix tool over the same page to generate the first call and page-cache
4. Review the same page in browser’s incognito-mode and find the clientIP set to GTMetrix’s.
This probably isn’t GDPR compliant. If I know a particular user’s IP address, I can use this JS to work out that user’s identifiers, and match any analytics data back to them. With this method you would no longer require consent for storing cookies under PECR, but you would now require consent for storing personal data under GDPR (if you didn’t already). I’m not a lawyer but that’s my reading of it.
Unfortunately hashed data still counts as personal data :(
https://law.stackexchange.com/a/32400/37030
I agree. The point is to replace the hash frequently enough to make it impossible to identify returning visitors after a very short time (a few days, or even hours).
I set up https://datacenturion.io for this reason. It’s a trackless analytics solution. Everybody gets a free account for 1000 visitors per month for free (for life). Give it a try, what have you. to lose?
Oh, and it’s available in light and dark modes plus is mobile friendly so you can check on your web analytics any time. I hope you enjoy the solution that is GDPR compliment so if your in the EU and don’t track any other way you can remove your cookie policy popup. At last, something that removes those stupid popups.
Now you can use Google Analytics with gtag.js using consent mode without cookies and with cookies after user consent: https://developers.google.com/gtagjs/devguide/consent
True, but without consent you won’t have any readable data for now (pings are collected, but statistics don’t show up in Google Analytics).
Have you tried it yourself? Anyone with a working solution?
Of course, I’ve tried it myself. After all, I’ve written it for this very site 😉
Thanks for your efforts. Is it from your understanding still necessary to mention Google in the data protection statement? You put it there with even mentioning that cookies are used.
Thanks
@Helge,
thank you for this article. I am currently running tests on several pages to check how the data changes here. I am already very excited.
I tried adding your code to my pages via tag manager (custom html). Surprisingly, I have no problem with the Matomo tag manager. With Google Tag Manager, when I try to publish the container, I get the following error message
“This language feature is only supported for ECMASCRIPT_2015 mode or better: const declaration.”
Unfortunately, I could not determine how to solve the problem. Do you have any idea how this occurs.
Best,
Lori
I cannot help with Google Tag Manager, but I can explain the nature of your issue:
The code is written in JavaScript (aka ECMAScript), a programming language that is evolving. New versions come with new features. In the case of my code, at least version 2015 is required (yep, already 6 years old, so I’m not requiring brand-new stuff).
This is more than a year ago, but to resolve the errors you need to replace all the ‘const’ and ‘let’ declarations to ‘var’. You also need to change on line 2 the part where it says ‘seed = 0’ to just seed and insert the following line right below (that would be line 3):
seed = typeof seed !== ‘undefined’ ? seed : 0;
This way you will solve all the ECMASCRIPT_2015 issues (const, var, and default).
Thanks for the code. In the code that retrieves ip address (cannot write it in full – I refer to array _SERVER variable REMOTE_ADDR) I had to replace the template tag with regular php tag, and it seems to work great.
Hi Helge,
I’ve tested your solution by adding the script (and not using the WP Plugin), but I still have one GA cookie laying around for the site on which I am doing tests. I’ve purged all cookies and checked the code, nothing else is triggering a GA cookie.
My test site is here : https://www.cow29.fr/
Tracking is ok and I don’t have js errors or else.
Also I don’t get what’s the unit of validityInterval, I’ve outputed it in console to see which value it had and I read : 4756, I hope it is not days ^-^’
Thanks for your feedback.
cyrb53 is not compatible with IE because of Math.imul. Polyfill can be found here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/imul#polyfill
The Polyfill is already part of the code. Take a look at the current version in the GitHub repo.
Hello Helge,
I’ve testing your code, it works perfectly!
Now I have a very basic question: how is Google Analytics still able to know my location if my IP address isn’t sent. This doesn’t make sense to me. Even when I replace clientIDHashed with a random sequence, I’m still perfectly located.
Hi Helge,
As you are using PHP then you can use your session id as your unique identifier.
Your code would be simplified to e.g.:
echo ”
(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’, ‘YOUR-GA-TRACKING-CODE’, {
‘anonymize_ip’: true,
‘storage’: ‘none’,
‘clientId’: ‘” . session_id() . “‘
});
ga(‘send’, ‘pageview’);
“;
Hi Helge,
I came across your plugin when I was looking for a way to use Google Analytics without cookies and I’ve been using it on my blog for a couple of days now. It works perfectly but yesterday I received a warning from WordPress saying that the plugin “seems to be abandoned” because it “hasn’t been update since 2020”. This made me wonder whether it is still GDPR compliant and safe to use?
Best regards,
Sarah
Hi
Using the plugin on https://vpninfo.dk and it works great. Thanks so much.
Google is bugging me about switching to a newer type of tracking. Will the plugin be updated to comply with that?
I’m not sure yet if I’ll update the plugin. I might ditch Google Analytics altogether.
Thank you for this amazing script, Helge! It works perfectly. But sadly only until June 2023
I would also be interested in an update 🙏🏻
Best wishes,
Anke
Hi,
Is the code compatible with GA4?
I have been using this for a few years on my website but I need to change the code, as Universal Analytics has been retired.
Hi Victor, I’ve also used this for a few years.
As I’m growing more and more wary of giving ever more data to Google, I’m not going to update this to Google Analytics v4. I removed all Google tracking from my websites.
This is now updated to use gtag and tested with WordPress 6. Helge merged my PR earlier today! I don’t know whether the version in the catalog will be updated, so it may be easiest to edit the plugin PHP file from the source code: https://github.com/helgeklein/Cookieless-Google-Analytics
Thanks for your contribution, Abram!