GA4 Custom Dimensions & Metrics
The gap GA4's defaults leave open
GA4's automatically collected and recommended event parameters cover a lot — page location, traffic source, device category — but almost no business actually operates only on Google's predefined vocabulary. Membership tier, content author, A/B test variant, loyalty points balance: none of these exist until you define them. GA4 captures any parameter you send on an event, but it won't surface that parameter anywhere in the UI (Explorations, standard reports, Looker Studio data source fields) until you explicitly register it as a Custom Dimension or Custom Metric in Admin > Custom Definitions.
Dimensions vs. metrics: the distinction that matters for querying
A Custom Dimension is a descriptive attribute — a string or category you'll group and filter by, like content_author or subscription_tier. A Custom Metric is a quantitative value you'll sum or average, like loyalty_points_earned or video_percent_watched. The distinction isn't cosmetic: metrics get aggregated (SUM, AVERAGE) in reports, dimensions get used for breakdowns and filters. Sending a numeric value as a dimension means you can filter by it but never sum it in a standard report — a frequent and easy-to-miss mistake.
Event-scoped vs. user-scoped definitions
GA4 supports two scopes for custom dimensions. Event-scoped dimensions attach to a single event and only have meaning in the context of that specific hit — content_category on a page_view. User-scoped dimensions attach to the user record itself and persist across every subsequent event and session until overwritten — subscription_tier set once at signup, then available on every report row for that user going forward, even sessions where you never resend it. User-scoped properties require calling gtag('set', 'user_properties', {...}) or the equivalent in GTM's GA4 configuration tag, not the event parameters object.
// User-scoped: persists on every future event for this user
gtag('set', 'user_properties', {
subscription_tier: 'premium'
});
// Event-scoped: only attached to this one event
gtag('event', 'view_item', {
content_category: 'editorial',
loyalty_points_earned: 25
});The 50-dimension / 50-metric ceiling
Registration lag and the BigQuery escape hatch
A parameter you start sending today is captured in GA4's raw event data (and in BigQuery export, if connected) immediately — but it won't appear as a selectable dimension in the GA4 UI or Looker Studio's GA4 connector until you register it, and standard reports only reflect data collected *after* registration, not retroactively. If you query BigQuery directly via UNNEST(event_params), this registration step is irrelevant — the raw parameter is there from day one. This is one of the strongest arguments for BigQuery export on any property doing custom tracking: you're not blocked by GA4's UI registration lag or its 50-dimension ceiling.
Custom dimension scope decision guide
| Need | Scope to use | Notes |
|---|---|---|
| A/B test variant seen on this pageview | Event-scoped | Varies per event; doesn't persist |
| User's lifetime subscription tier | User-scoped | Set once, applies to all future events |
| Per-product attribute on a purchase | Item-scoped | Doesn't count against the 50-dimension cap |
| Session-level traffic campaign detail beyond defaults | Event-scoped on session_start | Re-evaluate per session, not per user |
What's Next?
Once custom dimensions and metrics are flowing cleanly into GA4, the next practical step is querying them at scale and joining them against other systems — which is where BigQuery's nested schema and the Data API both become essential tools rather than optional extras.
I build these systems professionally.
Whether it's a RAG pipeline, analytics migration, or AI workflow — let's talk.