A total is rarely the answer; the split is. This guide covers the four breakdowns that turn GA4 from a wall of totals into something a marketer can act on: how sessions land in channels, how to separate brand demand from real acquisition, how to group content, and how to add your own dimensions at the right scope.
Channel groupings: how GA4 buckets your traffic
Open almost any GA4 report and the first dimension you reach for is the channel. "Where did this traffic come from?" Organic Search, Paid Search, Direct, Email, Referral. It's the language every marketer and every client speaks in. The problem is that GA4 doesn't actually store a channel anywhere. It stores a source and a medium, and then derives the channel on the fly from a fixed set of rules. Once you understand those rules, the reports stop surprising you.
This guide covers what each default channel really means, how GA4 picks one from the raw source and medium, and the handful of mistakes that send perfectly good traffic into the Unassigned bucket.
Why Channel Grouping Exists at All
You think in channels. GA4 thinks in source and medium. Those are two different layers, and the channel grouping is the translation between them.
Every session carries a source (who sent the traffic: google, facebook.com, newsletter) and a medium (the type of link: organic, cpc, email, referral). On their own, source and medium are precise but unwieldy. You don't want a report with two hundred source/medium rows. You want roughly a dozen channels. The Default Channel Group is GA4's built-in rulebook that collapses all those source/medium combinations into named channels you can report on.
How GA4 Assigns a Channel
GA4 runs each session through an ordered set of rules. It checks conditions against the session's source, medium, and sometimes the campaign name, and it also consults an internal list of known source categories, Google's classification of domains as Search, Social, Shopping, or Video. So when a session arrives from google, GA4 knows that's a search source; from facebook.com, a social source.
The rules are evaluated in order, and the first one that matches wins. That ordering matters: a session has to satisfy both the source-category condition and the medium condition for a paid channel before it falls through to the more general buckets. If nothing matches, the session lands in Unassigned.
| Default channel | Triggers when |
|---|---|
| Direct | Source is (direct) and medium is (none) or (not set): GA4 has no referrer and no campaign tagging to work from |
| Organic Search | Medium is organic, or the source matches a known search engine (like google or bing) with no paid medium |
| Paid Search | Source is a search engine and medium matches a paid pattern: cpc, ppc, or paid |
| Organic Social | Source is a known social network (like facebook.com) and medium is social, social-network, or similar non-paid social |
| Paid Social | Source is a known social network and medium matches a paid pattern (cpc, paid, or contains paid alongside social) |
Medium is exactly email (or the source/campaign clearly indicates email) |
|
| Referral | Medium is referral and the source isn't a recognised search or social property |
| Display | Medium is display, banner, cpm, or expandable (typically banner and remarketing inventory) |
That's the core set most reports live in. There are a few more (Affiliates, Audio, SMS, Mobile Push Notifications, Paid Shopping, Organic Shopping, Organic Video, Paid Video) but they follow the same shape: a source-category check combined with a medium pattern.
GA4 stores only source / medium — the channel is derived, in rule order:
The last row is the one to fear: a made-up medium matches nothing and the session lands in Unassigned. The fix is tagging discipline, not a GA4 setting.
A faithful-enough model of the default rules. Try (direct)/(none), newsletter/email, or partner-site.com/sponsorship.
Where It Goes Wrong
Channels are only as good as the tagging feeding them. Three problems cause the overwhelming majority of misclassified traffic.
Untagged campaigns. If you send a paid email blast or run a sponsored post and forget the UTM parameters, GA4 has no medium to read. The session arrives with whatever referrer the browser passed, or none at all, and you lose the channel entirely. Untagged paid traffic frequently shows up as Direct or Referral, quietly inflating channels that did no work.
Wrong medium values. This is the big one. GA4 matches medium against an expected list. If you tag a Facebook campaign with medium=social when GA4 expected the paid pattern, or you invent medium=newsletter instead of email, the rule that would have caught it never fires. The session falls through every channel condition and lands in Unassigned.
Your medium values must match GA4's expected list. A medium GA4 doesn't recognise satisfies no channel rule, so the session falls all the way through to Unassigned. The channel grouping can only sort traffic it can read.
Self-referrals. When a user crosses between your own subdomains, or returns from a payment gateway, your own domain can show up as the referring source. Without that domain on your referral exclusion list, GA4 may start a new session attributed to Referral from yourself, fragmenting the original channel.
UTM medium cheat sheet: use cpc for paid search, organic only for genuinely organic links (you rarely set this by hand), email for email, social for organic social posts, and display or cpm for banner buys. For paid social, use a paid pattern like cpc or paid-social rather than plain social. Match these exactly and the right channel falls out automatically.
The Unassigned Bucket
Unassigned is not a channel. It's the absence of one. A session ends up there when GA4 ran through every rule in the Default Channel Group and none of them matched, usually because the medium was missing, misspelled, or a value GA4 doesn't recognise.
A small amount of Unassigned is normal. A large or growing share is a signal: somewhere in your campaign tagging there's a medium GA4 can't classify, or a tracking gap leaking sessions. Treat a rising Unassigned line as a tagging audit waiting to happen, not as a fixed cost of doing business. The fix is almost always upstream, in how links are tagged, not inside GA4.
You Cannot Edit the Default Group
Here's the constraint that trips people up: the Default Channel Group is read-only. You can't change how it classifies cpc, you can't add a rule for your odd internal medium, and you can't rename Organic Search to something your client prefers. Google owns those definitions and updates them periodically.
What you can do is create a custom channel group, which sits alongside the default and lets you write your own ordered rules. That's how teams split Organic Search into Brand and Non-Brand, route a quirky in-house medium into the right named channel, or reclassify mis-tagged paid traffic without re-tagging the whole campaign history. The default stays untouched; your custom group is where your business logic lives.
Start With the Tagging
Default channel groupings feel like a GA4 setting you configure. They aren't. They're a downstream readout of the source and medium on every session, and those are decided the moment a link is clicked. If your channels look wrong, the lever is almost never inside the channel report, it's in the UTM parameters, the referral exclusions, and the consistency of the medium values your campaigns send. Get the inputs right and the default grouping does its job quietly. Get them wrong and no amount of staring at the channel report will fix it.
Brand vs non-brand traffic
Open almost any GA4 report and "Organic Search" sits there as one of your biggest channels, usually trending up and to the right, looking like proof that your SEO is working. It might be. But hidden inside that single number is a question it can't answer on its own: how much of that traffic is people who already knew you and typed your name into Google, versus people who found you while searching for something you sell?
Those are two completely different stories. One is demand you already created, coming home to roost. The other is new demand your marketing actually captured. Reporting them as a single line called "Organic" lets a brand-heavy quarter masquerade as SEO success, and it lets genuine non-brand growth get buried under a brand spike. Splitting them is the single most useful refinement you can make to a channel report.
Why the Split Matters
Branded search is downstream of everything else you do. When someone runs a TV ad, a billboard, a podcast read, a press mention, or even a strong paid-social campaign, a chunk of the people who see it later search your brand name to find you. That search lands in Organic. So when your "Organic Search" sessions jump, it might mean your content is ranking for new terms, or it might just mean your above-the-line spend worked and people came looking for you by name.
Non-brand organic is the number that tells you whether your SEO and content investment is winning new demand, the people who didn't know you existed before they searched. That is the harder, more valuable growth, and it is exactly the signal that a blended Organic number drowns out. If you are judging the marketing team, the agency, or the content budget on "Organic Search is up 30%," you may be congratulating the wrong department entirely.
Why GA4 Can't Do It Out of the Box
The frustrating part is that GA4 simply does not have the data to make this split for you. When a user clicks through from a Google organic result, Google does not pass the search query they typed to your analytics. It hasn't for over a decade, ever since the move to secure search stripped the keyword from the referrer. GA4 sees that the session came from google / organic, and that is all it knows.
Because the query is missing, every organic session lands in one undifferentiated bucket. There is no built-in dimension you can add, no segment you can build in Explore, that separates "searched our brand name" from "searched a generic term." The information that would let you tell them apart never reaches GA4 in the first place.
GA4 never sees the organic search query. Every organic session arrives as the same anonymous "google / organic," with no way to know whether the person searched your brand or a generic term.
Where the Query Data Actually Lives
The query exists, just not in GA4. It lives in Google Search Console, which reports on the search side of the click rather than the site side. Search Console knows the exact query for every organic impression and click, which is precisely the dimension GA4 is missing.
The implication is straightforward: the true brand vs non-brand picture comes from Search Console, and GA4 can only ever approximate it. Search Console is where you can filter queries that contain your brand name and read off real clicks for brand versus non-brand. The catch is that Search Console reports clicks and impressions, not sessions, conversions, or revenue, so it cannot tell you what that traffic did once it arrived. To connect the split to outcomes, you have to recreate an approximation inside GA4 itself.
Approaches to Approximate the Split
There is no single correct method, only a trade-off between how accurate the result is and how much work it takes to build. Here are the three practical approaches, ranked.
| Method | Accuracy | Effort |
|---|---|---|
| Search Console query data, filtered for brand terms | High (the only source with the real query) | Low to set up, but stays in clicks/impressions, not sessions |
| Landing-page heuristic (treat your homepage and brand pages as brand) | Medium (a reasonable proxy that leaks at the edges) | Low (one rule on existing GA4 data) |
| Custom channel group or rule keyed off brand landing pages or campaign tags | Medium to high, depending on how carefully you scope it | Medium (needs definition and maintenance) |
The landing-page heuristic rests on a real behavioural pattern: people who search your brand name tend to land on your homepage or a branded entry page, because that is what Google ranks first for a brand query. People searching a generic term land on the specific blog post, product page, or category that matched their intent. It is not perfect, but it correlates well enough to be useful, and it works entirely on data GA4 already has.
A Worked Rule Example
The simplest workable rule reads like this: take all sessions where the source/medium is google / organic, and classify the session as Brand when its landing page is the homepage (/) or sits under a known brand path (/brand-name*). Everything else from organic is Non-Brand.
In plain terms, the logic is:
- Session medium is
organicand landing page is/or starts with/brand-name→ Brand organic - Session medium is
organicand landing page is anything else → Non-Brand organic
You extend the brand path list to cover any URLs that only a brand-aware searcher would hit: your /about page, /contact, branded landing pages, your store locator. The rest of organic, the content and product pages that rank for what people are actually searching for, becomes your non-brand row. This is an approximation, and it will mislabel the occasional generic searcher who happens to land on the homepage, but it gives you two rows you can trend, compare, and tie to conversions, which the blended number never could.
Cross-check your rule against Search Console before you trust it. Pull the real brand-versus-non-brand click split from Search Console for the same period, and compare it to what your GA4 landing-page rule produces. If the two are roughly in line, your heuristic is sound. If brand looks far larger in GA4, your brand path list is catching generic traffic, and you should tighten it.
Reading the Two Rows Together
Once the split exists, the interpretation gets sharper. Brand organic climbing while non-brand stays flat usually means your awareness marketing is working but your SEO is treading water. Non-brand climbing is the signal that your content and rankings are pulling in people who didn't know you, which is the growth you can scale. And brand organic falling, even while total organic holds up, can be an early warning that awareness is slipping before any other channel shows it.
None of those readings are available from a single Organic line. The whole point of the split is to stop one story standing in for the other.
Build the Rule Once, Then Trend It
The split is worth setting up early, because like most analytics work it only becomes useful over time. A single month of brand versus non-brand tells you little; six months of the two rows trended side by side tells you whether your marketing is creating new demand or just harvesting demand you already had. Define the brand path list carefully, cross-check it against Search Console once, and from then on you have a channel report that distinguishes the work your marketing earned from the traffic that was always going to come.
Content grouping
Open the Pages and screens report on any real website and you are looking at hundreds of rows. Every blog post is its own line. Every product detail page is its own line. Pagination, query strings, and trailing slashes splinter what should be one number into ten. The report is technically complete and practically useless: nobody scrolling that list can tell you whether the blog is up this month or whether product pages are quietly tanking.
The question you actually want answered is about sections, not URLs. How is "the blog" doing against "pricing" against "product"? That requires grouping pages that belong together into named buckets. GA4 has a native mechanism for this, and it works, but it asks more of you than most teams expect.
The problem with page-path reports
Raw page paths are the wrong grain for almost every reporting conversation. A path like /blog/ga4-content-grouping tells you about one article. What a stakeholder wants to know is whether content marketing is pulling its weight overall, and you cannot read that off a list of 400 individual posts. You end up exporting to a spreadsheet, writing fragile =IF(LEFT(...)) formulas, and rebuilding the same buckets by hand every month.
Worse, the raw list is dishonest by omission. A category that is collapsing across fifty low-traffic pages never surfaces, because no single URL is big enough to notice. You only see the decline when the pages are summed into one row. Grouping is not a nicety here; it is the difference between seeing a trend and missing it.
GA4's native answer: the content_group parameter
GA4 ships a built-in dimension for exactly this, populated by a parameter called content_group. You set it on the page_view event, and GA4 exposes a "Content group" dimension you can drop into standard reports and Explorations with no custom definition required. It is one of the few reserved parameters that gives you a first-class reporting dimension for free.
| Page | Value to send | What it groups |
|---|---|---|
/pricing |
Pricing |
Every plan and quote page into one row |
/blog/* |
Blog |
All articles into a single "Blog" line |
/products/* |
Product |
Every product detail page together |
/, /about |
Marketing |
Home and top-of-funnel pages |
The value is just a string you decide on a per-page basis. The trick is computing the right string at the moment the page loads, which means your tagging has to know which section the current page belongs to before it fires the view. Here is what that looks like with gtag() directly and as the equivalent dataLayer push for GTM:
// gtag() direct: set content_group on the page_view
gtag('event', 'page_view', {
page_location: window.location.href,
page_title: document.title,
content_group: 'Blog' // computed from the URL/template
});
// Equivalent dataLayer push (for GTM)
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'page_view',
content_group: 'Blog'
});
In GTM you would read content_group from a Data Layer Variable and map it into the GA4 configuration tag's fields. On most sites the value comes from a CMS template name, a body class, or a regex on the path. However you derive it, the value has to be present and correct on every single page before the view fires.
The limits you will hit
The content_group parameter works, but it is strict in ways that catch teams out. None of these are bugs; they are the cost of doing the grouping at collection time rather than at report time.
- It only exists if you set it in code or GTM. There is no admin screen where you draw buckets. Every page must emit the right value, which means a tagging change and, usually, a developer or a GTM build.
- It is not retroactive. The dimension is populated from collected events only. Turn it on today and yesterday stays blank forever.
- Changing the logic restarts history. If you rename "Product" to "Shop" or split "Blog" into "Guides" and "News," the old and new values sit side by side and your trend line fractures at the changeover date.
- It is a single dimension. GA4 gives you one
content_groupslot per page view, so overlapping schemes (by topic and by funnel stage, say) are awkward; you have to pick one or fall back to additional custom dimensions.
Content grouping in GA4 is never retroactive. Whatever you do not tag today is blank forever, and the day you change your grouping rules is the day your historical trend line breaks in two.
If you must use content_group, drive it from a GTM lookup table rather than scattering literal strings across templates. Map a path-pattern variable to group names in one place, and you can adjust the buckets in a single tag instead of hunting through page code every time marketing renames a section.
The maintenance reality
For a team that controls its tagging and ships changes easily, content_group is the right tool. For everyone else, it is a standing tax. Plenty of sites cannot edit their tagging on demand: the GTM container is owned by an agency, the CMS templates are frozen, or every change goes through a release queue that turns a five-minute bucket tweak into a three-week ticket.
That friction has a compounding cost, because grouping is exactly the thing you want to revise often. Marketing launches a new section, you spin up a campaign landing-page hub, the product range gets reorganised. Each of those is a reason to re-cut your buckets, and with native content grouping each one means another tagging change and another break in your history. The mechanism that is supposed to make reporting clearer quietly becomes the thing you avoid touching.
Which approach to choose
If you own your tagging, ship freely, and want grouping baked into the raw data that every downstream tool reads, set content_group and accept that it is permanent the moment you turn it on. If you cannot easily edit tagging, or you expect your buckets to keep evolving, do the grouping at report time with rules over the page path instead. Either way, the goal is the same: stop reading four hundred URLs one at a time and start reading the handful of sections your stakeholders actually ask about.
Custom dimensions and scope
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.