GA4 custom dimensions and metrics without blowing up cardinality
Custom dimensions unlock the parameters you are already collecting — but register the wrong one and you get "(other)" rows swallowing your data. Here is how to pick, register, and scope them.
A GA4 event parameter is collected the moment you send it, but you cannot report on it until you register it as a custom dimension. Registration is not retroactive, so data only accrues from the moment you register — and registering the wrong kind of value is how properties end up with an "(other)" row swallowing half the report. Getting scope and cardinality right at the start is the whole job.
Collected is not the same as reportable
This trips up nearly everyone once. You add method to your sign_up event, confirm it in DebugView, then look for it in reports and find nothing.
GA4 stored it. It stores every parameter you send. But the reporting interface can only use parameters that have been registered as custom definitions, matched by exact name — and only from the registration date forward. Data collected before registration stays in the raw stream, reachable in BigQuery and invisible in the interface.
Two implications worth internalising:
- Register early, even for parameters you are not yet reporting on. There is no cost to registering something you do not use, and no way to recover the gap later.
- Names must match exactly, including case. A parameter sent as itemVariant and registered as item_variant produces an empty dimension.
The three scopes
| Scope | Describes | Example | Registered as |
|---|---|---|---|
| Event | One occurrence | method, button_id, form_name | Event-scoped dimension |
| User | The person, persistently | membership_tier, account_type | User-scoped dimension |
| Item | One product in a list | item_brand, item_variant | Item-scoped dimension |
Most custom dimensions should be event-scoped. Use user scope only for genuinely persistent attributes — a value that would be the same on every event that user ever sends. User-scoped dimensions are also more limited in number, and changing a user's value rewrites their attribution in ways that make historical comparison awkward.
Item scope only applies inside the ecommerce items array, and the distinction matters more than it looks — the items array deep dive covers where event-scoped and item-scoped data diverge in reporting.
The cardinality trap
This is the expensive mistake, and it is silent.
GA4 groups high-cardinality dimensions into an (other) bucket once a report exceeds its row limits. Register a dimension whose values are near-unique — a full URL with query strings, a user ID, a timestamp, a price as a string — and your real values disappear into that bucket. The report renders. The numbers are meaningless.
Rules that keep you out of it:
- Never register an unbounded value. Raw URLs, IDs, prices, timestamps, session identifiers.
- Bucket before you send. Instead of a price, send a price band. Instead of a full URL, send a normalised page type.
- Normalise case and spelling at the source. "Knitwear", "knitwear", and "Knit Wear" are three values.
- If you genuinely need per-record detail, use BigQuery, where cardinality limits do not apply.
Worked example: turning a parameter into a report
Say you want to know which signup method converts best. The parameter is method, with a deliberately small value set — google, email, apple. Three values, bounded forever, which is what makes it safe to register.
window.dataLayer.push({
event: 'sign_up',
method: 'google'
});
Register method as an event-scoped custom dimension named exactly that. Once data accrues, the dimension is available in explorations and in standard reports as a secondary dimension, so you can break signups — and any downstream key event — by method.
Now consider the version that fails. Someone decides method should carry more detail and sends google_oauth_v2_button_header_variant_b. Values now multiply with every experiment, the dimension goes high-cardinality, and the useful rows disappear into "(other)". The parameter still collects; the report stops being usable. The difference between the two versions is a decision made in about ten seconds, months before anyone notices the consequence.
Custom metrics
Custom metrics register numeric parameters for aggregation — a shipping cost, a quantity, a score. Same registration requirement, same non-retroactivity.
Two details:
- Choose the unit at registration — standard, currency, distance, time. It affects how the value is formatted and aggregated, and changing it later is disruptive.
- Do not register something as a metric that should be a dimension. A numeric ID is a label, not a quantity; summing it is meaningless.
The limits
On a standard property:
- 50 event-scoped custom dimensions
- 25 user-scoped custom dimensions
- 10 item-scoped custom dimensions
- 50 custom metrics
Generous, but not infinite, and archived definitions still occupy the quota until they are properly removed. Treat registration as a deliberate act with a documented reason — an ad-hoc registration culture fills the quota with parameters nobody reports on.
A workflow that avoids the usual pain
- Design the parameter in your tracking plan before implementing — name, scope, value set, who consumes it.
- Confirm the values are bounded. If you cannot enumerate roughly what the values will be, it is not ready to register.
- Implement the parameter and verify it in DebugView.
- Register it immediately, matching the name exactly.
- Wait for data, then build the report. Nothing appears retroactively.
- Review registrations quarterly and remove the ones nobody uses.
FAQ
Why is my custom dimension showing "(not set)"?
Usually one of three causes: the parameter name does not match the registration exactly, the events being reported on predate the registration, or the parameter simply was not sent on those events.
Are GA4 custom dimensions retroactive?
No. Data accrues only from the registration date forward, which is why registering early — even before you need the report — is the right default.
What is the difference between event-scoped and user-scoped dimensions?
Event-scoped describes a single occurrence; user-scoped describes the person and persists across their events. If the value would be identical on every event that user sends, it belongs in user scope.
Why do I see an "(other)" row in my reports?
High cardinality. The dimension has too many distinct values, so GA4 collapses the tail into a single bucket. Bucket the values at collection time, or move the analysis to BigQuery.
How many custom dimensions should I register?
As few as answer real questions — typically ten to twenty for most properties. The quota is a ceiling, not a target, and unused registrations make the interface harder to navigate.
Before designing dimensions, confirm the underlying events are actually reaching GA4: the free tracking audit checks tag presence, duplicates, and consent signals on any URL.
See where your tracking stands
Run the same 13-check audit referenced in this post against any URL. No signup, results in seconds.