adop.tools
All guides Custom dimensions

Event-Scoped vs User-Scoped Custom Dimensions

Which scope to register a custom dimension as, with the examples that go wrong when you pick the other one.

7 min read

You've sent a parameter, registered it as a custom dimension, and now it's reporting numbers that look almost right but never quite let you build the breakdown you wanted. The data is there. The dimension exists. And yet when you try to segment a whole report by it, half your sessions read (not set). Nine times out of ten, the cause isn't the data layer. It's the scope you picked when you registered the dimension.

Scope is the single setting on a custom dimension that decides how far the value travels through your reports. GA4 asks you for it once, in a small dropdown, and most people pick "Event" because it's the default and move on. That choice is silently deciding whether you can analyse an attribute across a user's entire journey or only on the one event that carried it.

What Scope Actually Means

Scope answers one question: what does this dimension describe, an event, a user, or an item? GA4 stores and joins the value differently depending on your answer, and that storage model is what limits where the dimension can show up.

An event-scoped dimension describes a single thing that happened. It's attached to one event and is only true for that event. A user-scoped dimension describes the person, not the action, and persists across every session and event that user produces. An item-scoped dimension describes a product inside the items array of an eCommerce event, and only makes sense alongside the other item fields. Pick the wrong one and the value still records, but it stops being joinable to the rows you want to analyse.

The Three Scopes, Side by Side

Scope Describes Use when the value is
Event A single interaction form_name, cta_location, content_type: true only at the moment the event fired
User The person customer_tier, account_type, lifecycle_stage: true about the user no matter what they're doing
Item A product in the items array item_category2, item_brand: an attribute of the product being viewed, added, or bought

The test is simple: ask whether the value would still be true if the user did something else. form_name changes every time a different form is submitted, so it belongs to the event. customer_tier stays "Gold" whether the user is reading a blog post or checking out, so it belongs to the user. item_brand only has meaning relative to a specific product, so it belongs to the item.

The Classic Mistake

The most common scope error is registering something that describes the user as event-scoped. It usually happens because the value got attached to one event in code, you set customer_tier as a parameter on the login event, registered it as an event-scoped dimension, and it works on the login report. So it looks done.

The problem shows up later. Because it's event-scoped, customer_tier only has a value on the login event itself. Every subsequent page_view, add_to_cart, and purchase in that same session carries no value for it. So when you build the report that actually matters, revenue by customer tier, conversion rate by customer tier, those purchase rows read (not set), because the purchase event never carried the parameter. You can see the tier on the one event that sent it, but not across the journey you wanted to segment.

Registered as a user-scoped dimension instead, and set as a user property in code, the value attaches to the user and applies to every event they fire, in that session and future ones. Now revenue by customer tier works, because GA4 joins the user attribute onto the purchase rows for you.

How to Register the Right Scope

Custom dimensions are created in Admin → Data display → Custom definitions. Click "Create custom dimension", give it a display name, choose the scope, and enter the exact parameter name (for event and item scope) or user property name (for user scope) as you send it in code. The name has to match character-for-character, customer_tier and customerTier are two different things to GA4, and a mismatch produces an empty dimension, not an error.

Two operational facts matter more than the form itself:

Registration is never retroactive: a dimension only fills from data collected after you create it, so the day you pick the scope is the day your usable history starts. And the standard quota is 50 event-scoped, 25 user-scoped, and 10 item-scoped custom dimensions (plus 50 metrics), so a scope you have to re-register later costs you a slot and your back-history both.

Why a Dimension Reads "(not set)"

When a custom dimension shows (not set) on rows where you expected a value, it's almost always one of three causes, and they're worth ruling out in order:

To pin down which cause you've got, open DebugView and fire the event live. If the parameter or user property shows in the event's detail pane, your code is fine and the issue is scope or timing on the registration. If it's absent from the pane entirely, it never left the page; fix the data layer first, before touching anything in the GA4 admin.

The Quota Forces a Choice

Standard GA4 properties allow 50 event-scoped, 25 user-scoped, and 10 item-scoped custom dimensions, plus 50 custom metrics. User scope is the tightest at 25, which bites because it is also the scope you most often need. That sounds generous until you realise every scope decision spends from it, and a dimension you registered under the wrong scope has to be replaced rather than edited, scope can't be changed after creation. You delete the old one, create a new one, and lose all the history collected under the original.

So the discipline is to decide scope before you register, not after you notice the report is wrong. Walk through your planned dimensions and sort each into event, user, or item by the "would it still be true if the user did something else" test. The ones that describe the person go to user scope even though it's slightly more work in code, because that's the only scope that lets you segment the whole property by them. Spending a few minutes on this up front is far cheaper than burning a quota slot and a few months of history on a re-registration.

In adop.tools

Once a custom dimension is registered in GA4, it becomes a selectable breakdown dimension and metric inside adop.tools, and you can drop it into a table or chart the same way you would any built-in field. That's exactly why the scope you choose up front matters here: register a user attribute as user-scoped and you can segment an entire report by it; register it as event-scoped and you'll only ever be able to break down the single event that carried it.

Related guides

See this in a real report.

Build a live GA4 report and share it with your client — free for one report.

Create a report →