Cross domain tracking in Google Analytics 4

On the face of it, implementing cross-domain tracking in Google Analytics 4 looks straightforward. It's a just an admin setting where you list the domains. GA will automatically append a _gl query parameter to any links that go between those domains. But what if you need some control over this process in case this automated decoration doesn't work? For example, maybe you have to use a URL fragment instead of query parameters or you need to decorate the action URL of a form. Fortunately, there is a manual tagging approach.

You set a linker gtag command before the config gtag command. Here is the process for gtag.js:

<script async src="https://www.googletagmanager.com/gtag/js?id=TAG_ID"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('set', 'linker', {'domains': ['example.com'], 'url_position': 'fragment'});
  gtag('js', new Date());

  gtag('config', 'TAG_ID');
</script>

The instructions however are only for gtag.js. For Tag Manager you are told to use the admin setting.

In Tag Manager, you have two types of GA4 tags: a configuration tag and an event tag. The config tag sends a gtag('config', ...) command. In your implementation you'll want to make sure that the config tag is triggered before any event tags.

So, how do you send a gtag linker command before the config gtag command? Well, I don't think you can. What I am doing in this case is use a combination of gtag and Tag Manager:

<script>
         window.dataLayer = window.dataLayer || [];
         function gtag() {
            dataLayer.push(arguments);
         }
         gtag('set', 'linker', {'domains': ['example.com'], 'url_position': 'fragment'});
         
</script>
<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-ID');</script>
<!-- End Google Tag Manager -->

There is nothing inherently complicated about the gtag function. It just takes in arguments and pushes those to the data layer.
Once Tag Manager executes the GA4 config tag it will find the set command in the data layer and honor it.

Job done? It depends...

The linker only decorates normal links. If you need to decorate say the action URL of a form post

<form method="POST" action="https://example.com">....</form>

then you are completely on your own and you have to decorate the links yourself, using the session and client ids.

gtag('get', 'TAG_ID', 'client_id', (client_id) => {
  // Store the client ID in a variable.
});
gtag('get', 'TAG_ID', 'session_id', (session_id) => {
  // Store the session ID in a variable.
});

  
// Once you have the client and session IDs, add them to the link that points to the destination domain:
// <a href="https://example.com/?clientId=XXXXX&sessionId=YYYYY">example.com</a>

// On the destination page, read the IDs from the URL and set them with the config command:

gtag('config','TAG_ID', {
'client_id': getClientIdFromUrl(),
'session_id': getSessionIdFromUrl()
});

Unfortunately there is no API to get a linker object directly, i.e. a method to retrieve the _gl parameter. Simo has a lot more to say about this.