Skip to content

Cookieless Google Analytics 4

Graeme Porteous edited this page Jul 11, 2023 · 1 revision

Currently with Google Universal Analytics we have an approach where we can use google analytics but not use cookies, and so do not need to set a cookie banner.

The downside of this approach is not having access to any figures on unique ‘users’, but in general having pretty good access to page view and event statistics, while not doing a big migration to a new service.

Unfortunately, we now have to migrate because they’re turning off Universal Analytics. The simplest thing to do is to upgrade to the new Google Analytics 4 (other options are available, but have cost, or self-hosting requirements), but this needs a slightly more complicated approach for running without cookies.

Backstory

Part of the reason for GA4 is Google is responding to a need 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 clever stuff makes it more complicated than before to have a cookieless approach. Running with analytics_storage consent denied doesn’t set cookies, but also turns off other features that could work fine, but don’t because it’s working into this expected flow.

How we fix this

To avoid this, we do two things:

  • Manually feed GA4 a randomly generated client_id with each page load.
  • Intercept GA4 setting cookies by overriding ‘document.cookies’.

The GA4 config option accepts a manual ‘client_id’ and a ‘cookie_expires’ that let 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 and we’re not tracking anyone around the site. Setting ‘cookie_expires’ to 1 means it expires quickly (1 second), if a cookie is accidentally set.

This is philosophically compliant - but is still setting cookies (even if they’re never moving information between views).

This doesn’t seem like an ideal approach (but once I’d got a version working, I found someone else had also found this solution), but we can do this by overriding ‘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.

My approach to this is in this gist https://gist.github.com/ajparsons/f5f34ec5b75a09d031f82d81c7329f3f - and only interferes with cookies starting with ‘_ga’, all other cookies can be read and set from javascript.

Minifying this, the final approach is in this gist: https://gist.github.com/ajparsons/3412d71ba3d726c43c65d9db014cd6a8

Replacing ‘{{ GA4_MEASUREMENT_ID }}’ gives an approach that still has the ‘users means page views’ issue, but real time statistics work, custom events work, and page_views work without the measurement protocol.

Clone this wiki locally