GA4 cross-domain for JavaScript redirects

Test Server GTM Promise API

Note: If you haven’t read David Vallejo’s blog yet, please stop wasting your time reading this post and read David’s post about GA4 cross-domain tracking.

Goal

Manually set up GA4 cross-domain tracking in cases when auto set up doesn’t work

Intro

GA4 makes cross-domain implementation very simple, just a few clicks in the admin interface and everything should work. You can read Simo Ahava post with all the details.

But if you need to set up cross-domain manually for example for JavaScript redirects, GA4 documentation gives very brief instructions, in short add client_id and session_id in a redirect url, and set them in GA4 config after the redirect. It sounds easy, but not as cool as it was for GA3. In GA3 we have a special linker helper which can decorate any link.

I made a Google-recommended implementation and even wrote a blog post about it, but during testing I faced very strange GA4 behavior. Despite that after redirection I sent the same client_id and session_id in GA4 report sessions become direct / none. I tried different variants and was ready to give up, then suddenly found a brilliant David Vallejo post about cross domain tracking.

David Vallejo in his post mentioned that analytics.js has utility method google_tag_data.glBridge.generate which can create valid _gl param. I don’t know how he found it, for me it’s next to impossible. He must have been in the middle of rewriting out of pleasure analytics.js for C++ or Assembler and found this tiny little utility. David is really the best.

Tag solution

Unfortunately, we can’t use custom templates, as Google doesn’t allow us to access google_tag_data from templates anymore.

The idea remains the same: we will create the chtml.setup_gl HTML tag, which generates the dataLayer event with _gl:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<script>
  try {
    if (window.google_tag_data && window.google_tag_data.glBridge) {
      var cookieObject = {
          _ga: {{cookie._ga}}.substr(6),
      }
      cookieObject["_ga_" + {{const.ga4.mid}}] = {{cookie._ga4_session}}.substr(6);
      var gl = window.google_tag_data.glBridge.generate(cookieObject);
      if (gl) {
        dataLayer.push({
          event: "gl_ready",
          gtagApiResult: {
            _gl: gl,
          },
        });
      }
    }
  } catch (error) {
    console.log('error', error);
  }
</script>

The code is very simple, we call window.google_tag_data.glBridge.generate() and pass two cookies:

  • _ga - clientId cookie
  • _ga_<Measurement Id> - session cookie (in my case _ga_WSNZDRC2JS)
GA4 cookies

That’s all we need. So to get these cookies, I defined a few cookie variables:

  • {{cookie._ga}} - with Cookie Name _ga
  • {{cookie._ga4_session}} - with Cookie Name _ga_{{const.ga4.mid}}

And also a constant variable {{const.ga4.mid}} with Measurement Id in my case WSNZDRC2JS.

The important note

This solution uses analytics.js, so it will work only after the library is loaded. If you don’t use UA anymore, you still need to inject the library, for example, using an HTML tag:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<script>
(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','https://www.google-analytics.com/analytics.js','ga');

ga('create', 'UA-XXXXX-Y', 'auto');
ga(function(tracker) {
  dataLayer.push({event:"ga_ready"});
}); 
</script>

And now you can use the ga_ready event to trigger the previous tag to generate _gl.

Previous solution, currently not working

Based on David’s blog post I created a GTM custom template which makes it easier to generate a _gl parameter. But please keep in mind, this template uses glBridge utility, and this utility is a part of analytics.js. In 2023, Google is going to shut down GA3 and analytics.js might stop working or turn into a pumpkin.

Template logic

  1. The template get _ga and _ga_YOUR_MEASURMENT_ID cookie values and pass them into google_tag_data.glBridge.generate method.
  2. google_tag_data.glBridge.generate method returns a value for _gl parameter.
  3. The template makes a dataLayer push with _gl value.
  4. You can get the value from a dataLayer and add _gl parameter to any link you need

Template usage example

Note: I sent the template for submission in the Template Gallery, but at the moment you can download it by this link .

  1. Add template to your landing page GTM container. Go to the Template Page, click New in the Tag Templates section. In the left menu select Import and select the template file . And Save template after importing.

  2. Create template’s tag. Go to the Tags page and add a new tag based on the template.

  3. In the Measurement Id field set your GA4 data stream id.

GA4 Cross-Domain Linker tag
  1. You should load analytics.js before using the template, it’s not a problem if you use GA3 alongside with GA4. Otherwise, the template has a checkbox called load analytics.js, you can check it and the template will load the library.

  2. Also in Additional Settings you can add cookie prefix if for some reasons you changed it in GA4.config. You can also change dataLayer event name, as by default it is linker_ready.

GA4 Cross-Domain Linker ettings
  1. Add the tag as a cleanup tag for the GA4 Configuration tag. It is guaranteed that _ga cookie is set, and the tag can get its value. If a user can spend more than 2 minutes on a landing page, it’s better to add a timer trigger for the tag, just to refresh _gl value. The GA3 linker param was valid for 2 minutes, I don’t know the number for GA4, but I expect it should be the same. Another variant instead of using a timer trigger where you can trigger the tag just before the redirect.
GA4 Cross-Domain Linker trigger
  1. Create a dataLayer variable to get _gl value.
GA4 Cross-Domain Linker variable
  1. If you will decorate links from GTM, you can create custom html tag and add something like this:
1
2
3
4
5
6
7
8
function makeRedirect() {
 var url = "https://gtm-gear.com/";
 url += "?_gl=" + {{dlv.ga4_linker}}
 document.location.href = url;
}
 
var button = document.querySelector(".button");
button.addEventListener("click", makeRedirect);
  1. If developers will decorate links from code they can get _gl value from dataLayer for example like this, I assume that you have one GTM container on a landing page:
1
2
3
4
5
const ga4Linker = google_tag_manager[
   Object.keys(google_tag_manager).filter(
     (key) => key.toUpperCase().indexOf("GTM") > -1
   )[0]
  ].dataLayer.get("ga4_linker")

And ask developers to add this value to the url before redirect, something like this:

1
url += "?_gl=" + ga4Linker

That’s all, thanks for reading. I hope you will find the template helpful, and as usual if you have any questions or ideas please message me on LinkedIn