Universal Analytics utilizes two components (by default) to attribute a browser session to a specific campaign: query parameters in the URL and the referrer string.
The page URL is sent with every hit to Google Analytics using the Document location field, which also translates to the &dl
parameter in the Measurement Protocol.
The referrer string is sent with a hit to Google Analytics using the Document referrer field, as long as the referrer hostname does not exactly match that of the current page and the referrer string is not empty.
When attributing a hit to a campaign session, Google Analytics first checks the location value for the following parameters:
gclid
- for attributing the session to Google Adsutm_source
- for attributing the session to a custom campaign
If these parameters are not found, Google Analytics looks at the referrer string.
Yes, there are additional steps to campaign attribution (see the flowchart), but these two are relevant in the context of this article.
There are two contexts where attributing campaigns based on URLs and referrer strings becomes complicated.
- If the site blocks Google Analytics on the landing page (due to missing consent), but then makes it available on subsequent pages (due to granted consent), the original campaign URL and referrer string are no longer available.
- If the site is a single-page application, particularly Google Tag Manager-based tags become susceptible to the rogue referral problem.
For this purpose, I have created a new custom template: Persist Campaign Data.
This template tackles both of the problems listed above. However, you will need to make additional changes to your Google Analytics settings before the solution is complete.
You can download the template from the template gallery.
The Simmer Newsletter
Follow this link to subscribe to the Simmer Newsletter! Stay up-to-date with the latest content from Simo Ahava and the Simmer online course platform.
1. Store campaign data in a session cookie
The first problem is particularly bothersome on sites that utilize consent management systems to block Google Analytics if consent has not been granted.
If consent is granted on the landing page, then there should be no issues. The page is reloaded with the campaign parameters in the URL and the referral string intact, and Google Analytics tags can make use of these when attributing the session to a specific campaign.
However, if the user grants consent only after navigating deeper into the site, the tags that fire on these pages will no longer have access to the initial campaign that brought the user to the site, which leads to inflation of direct traffic.
To solve this problem, the template tag (assuming Google Tag Manager is not blocked, too) writes the URL of the current page in a cookie if it has campaign-setting parameters. Similarly, the referrer string is also written in a cookie if its hostname does not contain the current page hostname.
Then, when the Google Analytics tags finally fire, they can take the original location and referrer from these cookies to attribute the session to its correct source.
Setup the Persist Campaign Data tag
Create a new tag with the Persist Campaign Data template. Check the box next to Store campaign data in a browser cookie and check that the settings are OK.
By default, the following URL parameters will cause the tag to set the campaign URL cookie:
utm_source
utm_medium
utm_campaign
utm_term
utm_content
utm_id
gclid
Naturally, you can add additional parameters to the list if you wish.
Not all of these parameters have the ability to start a new session (only utm_source
and gclid
do, in fact), but the assumption is that if you’ve set any UTM parameters, you want to include them with your hits.
Next, you can change the default name of the URL cookie and the referrer cookie, if you wish.
NOTE! If you do change the name of either cookie, you must edit the template permissions and allow the template to set the new cookie name(s).
The tag will only write the cookies in specific conditions:
- The URL cookie is only written if the URL query string has any one of the listed trigger parameters. If such a parameter is found, the full URL is written into the campaign URL cookie.
- The Referrer cookie is only written if the referrer hostname does not contain the current page hostname, and the referrer string is not empty. If both conditions are good, the full referrer string is written into the referrer cookie.
Thus it’s safe to set it on the All Pages trigger, as it doesn’t matter if it fires on every single page.
Note! You might want to actually set it on a trigger that fires once user consent has been established - if the user gives consent or has already given consent, the tag does not need to (and should not) fire, because then your Google Analytics tags would fire normally and your wouldn’t need to save the URL and referrer data in cookies.
Create the cookie variables
You’ll need two new, user-defined 1st Party Cookie variables.
The first, {{Cookie - __gtm_campaign_url}}. The second, {{Cookie - __gtm_referrer}}.
Make sure you check the URI-decode cookie box.
Create the customTask
To update your Google Analytics tags, we’ll be using a customTask
. This is an easy way to make sure the cookies are only used if they exist, and it’s a great way to actually delete the cookies as soon as they’ve been used, so that they don’t persist needlessly.
NOTE! You don’t have to use a
customTask
. You can set thelocation
,page
, andreferrer
fields manually on the tag, if you wish. You just need to add proper fallback values if the cookies do not exist, otherwise you risk corrupting your data with invalidlocation
andreferrer
fields.
If you need the cookies for something else as well, then you can set the customTask
to not delete cookies after the values have been “used”.
Create a new Custom JavaScript variable, name it {{customTask - set location and referrer from cookie}}, and add the following code within:
function() {
return function(customModel) {
// Set to the cookie variables
var storedLocation = {{Cookie - __gtm_campaign_url}};
var storedReferrer = {{Cookie - __gtm_referrer}};
// Set to the cookie names
var storedLocationCookieName = '__gtm_campaign_url';
var storedReferrerCookieName = '__gtm_referrer';
// Set to the root domain of your site (e.g. domain name without any subdomain, "mysite.com")
var domainName = 'simoahava.com';
// Choose whether to delete a cookie after its value is used (true/false)
var deleteCookieToggle = true;
var deleteCookie = function(name) {
document.cookie = name + '=; path=/; domain=' + domainName + '; expires=Thu, 01 Jan 1970 00:00:00 GMT';
};
if (typeof storedLocation !== 'undefined') {
customModel.set('location', storedLocation);
customModel.set('page', document.location.pathname + document.location.search);
if (deleteCookieToggle === true) { deleteCookie(storedLocationCookieName); }
}
if (typeof storedReferrer !== 'undefined') {
customModel.set('referrer', storedReferrer);
if (deleteCookieToggle === true) { deleteCookie(storedReferrerCookieName); }
}
};
}
There are some things you might need to edit.
Change the storedLocation
and storedReferrer
variables to point to the correct Google Tag Manager 1st Party Cookie variables you’ve created, if you’ve diverted from the default values used in this guide.
Change the storedLocationCookieName
and storedReferrerCookieName
to the correct cookie names, if you’ve diverted from the default values used in this guide.
Set the domainName
to the correct root domain (domain name without any subdomains included).
Set the deleteCookieToggle
to false
if you don’t want the task to remove cookies once their values have been “used”.
This customTask
sets the location
andreferrer
fields if the respective cookies exist, and then (optionally) deletes the cookies for those fields that were set. If a cookie doesn’t exist, its field is not set.
If the location
is set, the customTask
also sets the page
field to the current page path and query string. If it didn’t do this, then the tag would take the page path from the location
as well, which would inflate the pageviews of the landing page rather than increment them for the page the user is actually on.
Add the customTask
to your Google Analytics tags
Finally, add the customTask
variable you just created to your Google Analytics tags. You only need to have it fire on the first tag that fires on the page, but just to be safe it might make sense to add it to your Google Analytics Settings variable, and then attach that to all your tags.
The field name is customTask and the value is {{customTask - set location and referrer from cookie}}.
Store campaign data - summary
If the user lands on your site with campaign parameters in the URL and/or a referrer that is external to your website’s domain, the following happens:
- The Persist Campaign Data tag creates new cookies where necessary; one for the URL of the page (if it had campaign parameters) and one for the referrer string (if it’s an external domain).
- Once the user gives consent, your Google Analytics tags utilize a
customTask
which sets thelocation
andreferrer
fields from the cookies, and subsequently deletes the cookies. - Thus the campaign URL and the referrer string are preserved for Google Analytics tags that might not fire until later in the site navigation, when the original URL parameters and referrer string are just a memory.
There are some caveats to note.
- If you already have a
customTask
set, you need to modify it to include thecustomTask
code from this guide. See this for inspiration. - There might be a race condition where the Persist Campaign Data tag completes after the Google Analytics tag has fired, leading to cookies being created but not utilized (and subsequently deleted). An easy way to avoid this problem is to make sure the Persist Campaign Data tag only fires if user has not given consent. That way it will only fire if the Google Analytics have been blocked.
- The cookies are session cookies so if the user closes the browser and then re-opens it to continue navigation, the stored data is lost.
- Cross-domain tracking is something that is very likely lost if you delay firing your tags beyond the landing page - this solution will not help with this problem. You could take the Client ID from the linker parameter, but since you’re not validating the linker parameter, the data would be vulnerable to link sharing (and any users who follow the shared link would be counted as the same user by Google Analytics).
2. Push original location in dataLayer
The rogue referral problem is a scenario where tags firing on a single-page app use the “virtual” location for campaign attribution (often missing the original campaign parameters) and thus Google Analytics reverts to the referrer instead. This results in sessions where hits sent on the first page are part of a google / cpc
session, but as soon as the user navigates to other parts of the SPA, the campaign changes to the referral source, such as google / organic
.
NOTE! Please understand that these steps must only be taken if the site is a single-page app or has single-page app components. There’s no harm in setting this up on a regular site, but it’s just extra work and clutter in the container.
To fix this, you need the following.
Setup the Persist Campaign Data tag
Create a new tag with the Persist Campaign Data template. Check the box next to Push original location in dataLayer and check that the settings are OK.
Set this tag to fire on the All Pages trigger.
This tag, when fires, does a very simple thing. It writes the current URL into the dataLayer
using the key originalLocation
.
It’s important that this tag only fires once on the initial page load (hence the All Pages trigger).
Create the Data Layer Variable
Next, create a Data Layer variable named {{Original Location}}, and set it to point to the originalLocation
key. Importantly, set its Default Value to {{Page URL}}.
The Default Value is very important. It’s more than likely that a Google Analytics tag fires before
originalLocation
is pushed intodataLayer
, so the variable simply returns the current page URL (which should be the same asoriginalLocation
) in case such a race condition emerges.
Update your Google Analytics tags
Now that you have the template tag firing on the All Pages trigger, and you have the Data Layer variable ready, you need to edit every single one of your Google Analytics tags. Easiest way to do this is with the Google Analytics Settings variable.
NOTE! It’s vital that every single Google Analytics tag has this modification. If there’s even a single tag that fires without the fixed location, it’s possible that you campaign data will be broken.
You need to add two fields to the variable:
Field name: location
Value: {{Original Location}}
Field name: page
Value: {{Page Path}}
The value of page
should be whatever you currently use for your virtual pageviews on your single-page app. It could be a Data Layer variable, a history state variable, or it could simply be the current page URL or current page path, as in the example above.
Both of these fields are required to be set in your tags.
How it works
Once you’ve set it up, here’s what happens.
- When the user lands on the page, the page URL is stored in the
originalLocation
Data Layer variable. - When a Google Analytics tag fires, it takes the location field from this variable. This is because the location field is used for campaign attribution, and the landing page has all these parameters.
- The page field is set so that a virtual page path could be set for your single-page app page views.
Thus by always sending the URL of the initial page load as the value of the location field, the rogue referral problem is fixed.
Summary
The Persist Campaign Data template addresses two problems with campaign tracking in Google Analytics.
- Delayed / deferred Google Analytics tracking, where the first hits of the session are sent after the user has navigated away from the landing page.
- Single-page application tracking, where campaign sessions get incorrectly attributed to the referrer string rather than the parameters in the URL.
You can, of course, utilize the features for other things as well. Persisting campaign parameters can have uses beyond Google Analytics tracking, and identifying the URL of the first page load of a SPA could similarly be used to understand navigation patterns better.
As always, I look forward to hearing from you in the comments. Do you see problems with either approach? Do you have suggestions for improving the template or the implementation thereof?