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:
- Scope is chosen at registration, against the matching code. A user-scoped dimension reads from user properties you set with
gtag('set', 'user_properties', {...})or a user-property tag in GTM, not from event parameters. Event-scoped dimensions read from event parameters. The two have to agree. - Registration is not retroactive. The dimension only populates from events collected after you create the definition. If you register
lifecycle_stagetoday, every row before today stays(not set)forever, with no backfill.
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:
- The parameter wasn't sent on that event. The dimension is event-scoped and the value rides on a different event than the one you're looking at. This is the classic mistake above, the value exists, just not on these rows.
- Scope mismatch. You registered a user attribute as event-scoped (or the reverse), so GA4 is looking for the value in the wrong place. The value is being collected; it just isn't joined to the rows you're querying.
- Registered after the data. The events predate the custom definition. Because registration isn't retroactive, older rows can never fill in, no matter how the parameter was sent.
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.