Scope in Google Analytics’ Custom Dimensions refers to how the value in the Custom Dimension is extended to all hits in the same scope.
Hit- and product-scoped Custom Dimensions apply to the given hit alone - they are not extended to any other hits in the session or by the same user.
Session-scoped Custom Dimensions apply the last value sent during the session to all the hits in that session.
User-scoped Custom Dimensions apply the last value set during the session to all the hits in the session AND to all future hits by the same client until a new value is sent.
Until now, I was under the impression that User-scoped Custom Dimensions could never be unset, because I thought it was not possible to send a “null” or “empty” value to a Custom Dimension. The analytics.js library, used by GTM for web and on-page Universal Analytics, prevents null values from being sent with Custom Dimensions (but not empty strings), so this issue never manifested itself in my implementations.
However, sending hits with Measurement Protocol or by using the legacy Google Tag Manager for iOS/Android SDKs can have you run into this issue. Here’s what it looks like in reports:
I ran into this problem while looking at some erratic client data. For some reason, it looked like a user-scoped Custom Dimension that was set in one session was no longer available at a later session. In the image above, you can see what this looks like.
The data is from one single user who had two successive sessions, some four hours apart. The first session, at 10:49, had two screen views, and you could query this data with the user-scoped Custom Dimension, returning the value b
.
Then, in the latter session with just one screen view, you could no longer query this user-scoped Custom Dimension.
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.
How to send a parameter without a value
If you’re using Measurement Protocol, you can easily reproduce this. All you have to do is copy an existing hit from your website, which includes a user-scoped Custom Dimension, and resend it by replacing the Custom Dimension value with nothing.
Let’s say the request originally had these parameters:
&cd5=page&cd6=logged-in&cd7=this-user-id&cd8=hello
Here, dimension &cd7
is user-scoped in Google Analytics, so if this were the last hit sent in the session, the user would have the value this-user-id
until some other, valid value is sent to the Custom Dimension.
However, if I now copy this hit request and replace the relevant part with this:
&cd5=page&cd6=logged-in&cd7&cd8=hello
You can see how I removed the value from the parameter, but I still include the parameter in the query string (you could also use &cd6=logged-in&cd7=&cd8=hello
and it will have the same result). Now, the value is sent to Google Analytics, and it essentially nulls the value from the GA reporting interface, meaning you no longer can query this user’s user-scoped Custom Dimension.
In BigQuery, this manifests as something like:
As you can see, there’s a bunch of Custom Dimension indices with empty strings as their values. If any one of these corresponds with a user-scoped or session-scoped Custom Dimension, it would reset any previous value this field had.
Where the problem lies
Naturally, most people will be using GTM and GA for the web, so this problem will never manifest.
However, Google Tag Manager’s legacy SDKs (iOS and Android) have a quirky feature.
If you’ve defined a Data Layer variable in your Google Analytics tag, and that Data Layer key is not populated with a value when the tag fires, then the SDK will automatically send the parameter without a value, thus nulling any user- or session-scoped Custom Dimensions that receive these empty parameters.
Here you can see an example of what a request to GA looks like when using the normal Universal Analytics tag in the legacy iOS SDK. As you can see, there’s a bunch of Custom Dimensions (circled in red) which do not have any value. These are dimensions that had no value in TAGDataLayer
when the hit was dispatched.
This issue is not present in the latest GTM SDK (Firebase), which is a good thing, of course.
Also, I didn’t test if this problem is present if using the native Google Analytics SDK. I would imagine that it is, because the legacy GTM SDK uses the Google Analytics Services SDK to dispatch the hits to GA.
What this means
This has two implications.
First, if you’re using the legacy Google Tag Manager SDKs and most likely the Google Analytics Service SDK, too, it’s possible that any user-scoped or session-scoped Custom Dimensions will have corrupt data.
It would be a fair assumption to make that if the key does not have a value in Data Layer, the key is not included in the hit, or that at the very least, a valueless parameter will NOT override a previously set value, but these assumptions are, based on my findings, wrong.
Thus you’ll need to rethink your approach in implementation, perhaps making sure that tags that should send user- and session-scoped Custom Dimensions do not fire unless the Data Layer variables resolve to proper values. This might mean creating new tags in your container.
Second, this can also be a good thing. There are times when resetting Custom Dimensions is necessary. For example, if you’re using a user-scoped Custom Dimension to store experiment information from A/B tests, you might want to reset these fields for users who have been in an earlier test but are no longer included in a currently running test. You know, just to keep the amount of noise down in your reports.
To do this, you could write some code which checks if the user is included in a current experiment and if not, send the dimension without a value.
You can do this easily in the web, too. Just set the dimension value to an empty string:
ga('tracker.set', 'dimension25', '');
// or
ga('send', 'event', 'resetDimension', 'reset', {dimension25: ''});
If you’re using Google Tag Manager for web, it’s a bit more difficult since you can’t set empty fields. However, it’s easy enough to just create a Custom JavaScript variable which returns an empty string:
The Custom JavaScript variable would look like this:
function() {
return '';
}
Final thoughts
I think it’s good that we have the option to reset a Custom Dimension value. And yes, I know it’s not technically resetting the value, but more about exploiting a feature of the Google Analytics reporting interface which prevents you from querying Custom Dimensions with empty values.
However, I think it’s bad that the mobile SDKs (Firebase excluded) are doing this automatically. It’s always confusing when Google Tag Manager for apps diverges from how it works on the web, and this is another example of it doing so. Instead of dropping the parameter from the requests if no valid value is found, as it does on the web, the parameter is sent with an empty value.
I don’t expect this to be fixed in the legacy containers, since they are expected to move out of the way of Firebase, and this is certainly yet another reason to upgrade your setup.
You should probably look through your data for this phenomenon, especially if you’re collecting app data using one of the legacy SDKs. If you have GA360 and access to the BigQuery exports, you can query for Custom Dimension fields that have an empty string as their value.
In the Google Analytics user interface, I originally found this problem by creating a segment where I include users that have a valid value for a user-scoped Custom Dimension but exclude sessions that don’t have a valid value.
This segment would thus include data from users with sessions that DO have a valid value and sessions that DON’T have a valid value. After that, it’s a question of using the User Explorer report and drilling down to individual user journeys to see if the valueless sessions came after those that had a valid value.