Our recipe for cookieless Google Analytics 4

We use Google Analytics to understand how many people visit our sites and services; though we’re careful not to collect personal information about our users, or to ‘follow’ them around the web.

With the old version of Google Analytics, we had an approach where we turned off storage (with ‘client_storage: none’) and so did not need to display cookie banners to visitors the first time they visited each of our sites. The downside of our approach was losing access to any figures on unique ‘users’, but we still had pretty good access to generalised page view and event statistics, plus a simpler, banner-free experience for our visitors.

Unfortunately, this year we’ve had to migrate to Google Analytics 4, because they’ll be turning off the previous version (“Universal Analytics”) in 2024.

The old ‘client_storage: none’ setting doesn’t work in GA4, so this blog post outlines our new, slightly more complicated approach, for running our websites without cookies.

If you just want the code, you can get it here.

Backstory

GA4 represents Google trying to work better with requirements for people to consent to cookies before turning on analytics cookies. The expected flow is that someone will arrive at a site, see a cookie banner and hit ‘allow’ or not.

Because of this, GA4 has more built-in approaches for working with consent that controls when it sets cookies. Behind the scenes GA4 communicates some stuff anonymously when activated (with enough opt-ins, it starts using this and machine learning to give you fake data for your non-opt-in visitors), and holds back other stuff (eg: custom events) until the fully opted-in version is turned on.

This fiddly stuff makes it more complicated than before to have a cookieless approach. Running with the ‘analytics_storage: denied’ consent setting doesn’t store cookies, which makes sense. But it also turns off other features—like real time statistics, custom events, and page_views—that could work fine, but don’t, because GA4 assumes you’ll eventually enable ‘analytics_storage’ once the user gives consent. GA4 is effectively useless in this mode.

How we fix this

To get the benefits of Google Analytics without letting it set any cookies, we do two things:

  • Feed GA4 a randomly generated client_id with each page load.
  • Intercept and prevent GA4 setting cookies by overriding ‘document.cookies’.

The GA4 config option accepts a manual ‘client_id’ that lets you effectively make each page view anonymous. The client_id is two 32-bit integers separated by a dot, and generating a random one each time makes each page view independent so we’re not tracking anyone around the site.

Setting a random ‘client_id’ on each page load is philosophically compliant with our privacy stance that we don’t want to track users moving around our sites. But it’s still setting cookies without consent (even if they never move information between views, and they expire quickly), which runs afoul of PECR.

So, to go further than this, we monkey patch  ‘document.cookies’ so that when GA4 tries to write or read cookies, it’s writing nowhere and is reading a random client_id that hasn’t been stored anywhere. A similar example can be seen in this StackOverflow comment. Google thinks it’s reading and writing cookies, but no cookies ever get stored on the user’s device.

The final template can be found in our GitHub repo. Feel free to use it too!

Replacing ‘{{ GA4_MEASUREMENT_ID }}’ in this template gives an approach that still has the ‘users means page views’ issue, but real time statistics, custom events, and page_views work as expected.

Photo by Vyshnavi Bisani on Unsplash