Content Groups in GA4: Reporting by Section, Not by URL
Open the Pages and screens report on a content site with more than a thousand URLs and the top row is almost always the home page. Below it sits a long tail where every row carries a different number of views, and none of the rows answers the question anyone actually asks, which is whether the guides hold attention better than the news posts.
That question is about a section, not a URL. GA4 has one parameter for it, and setting it up properly takes an afternoon.
The URL is a poor unit for a content site
A page URL answers one question well. Did this particular page get seen. Almost everything else a content business needs is a comparison between kinds of page. Article against guide. Evergreen resource against this week's commentary. Documentation against blog.
The long tail makes that harder. A site publishing 40 posts a month accumulates close to 500 URLs in a year, and most of them collect fewer than 30 views in a given month. Ranking the list tells you the obvious and hides the pattern, which lives in the average across a section.
What a content group is
Google's help page is short and plain. Content groups "let you categorize pages and screens into custom buckets", so you can see metrics for related groups of information. Google's own example is a clothing retailer splitting men's pages from women's. The mechanic is the same for a publisher, with sections in place of departments.
Under the description it is one event parameter, content_group, carrying a text value. Whatever you put in that value becomes a row in the Content group dimension, which you then select in the Pages and screens report or add to an exploration.
It is not a saved comparison, not a segment, and not the old Universal Analytics content grouping screen. There is no place in GA4 admin where you write a rule and save it. The label arrives with the hit or it does not exist.
Nothing re-labels history. Pages collected before the parameter shipped report as (not set) for as long as those dates sit inside the retention window.
Two ways to set the value
With the Google tag, add the parameter to the config command on each page:
gtag('config', 'G-XXXXXX', {
'content_group': 'guides'
});
That is one line per template, filled in by the CMS or resolved server side. The value has to be correct when the tag fires, because the config call is what carries it.
With Google Tag Manager, the documented route is a RegEx Table variable. Create a user-defined variable of type RegEx Table, name it Content Group, set the Input Variable to {{Page Path}}, and add a row for each section with a pattern and an output. Google's example maps /mens to "Men's clothing page" and /womens to "Women's clothing page". Under the variable's Advanced Settings, clear Full Matches Only and clear Enable Capture Groups and Replace Functionality. Then open the Google Analytics: GA4 Configuration tag, add a row under Fields to Set with the field name content_group and the value {{Content Group}}, and publish.
On a Firebase app the same parameter goes on the screen_view event.
The RegEx Table is right when the group can be read from the path, which covers most sites. It is wrong when the group depends on something the URL does not carry, such as a CMS taxonomy where the section is a term rather than a path segment. In that case push the value into the data layer from the template and read it with a data layer variable.
Decide the taxonomy before anyone writes a tag
The parameter is trivial. The taxonomy is the work, and it is the part that gets skipped. Three decisions, in order.
First, which question the report will answer. Not "how is content doing", which has no answer. Something with a comparison inside it, such as whether guides hold attention better than commentary, or whether case studies earn the visit the sales team's emails send them.
Second, how many levels. One is usually enough to start. Google notes that you can add content_group2 and content_group3 for a hierarchy, for example top level, then apparel, then shirts. Each extra level is another custom parameter, and it reports nothing until you register it as a custom dimension.
Third, how stable the labels are. A group named after this quarter's campaign is meaningless in a year, and the history cannot be rewritten. Name groups after permanent structure, so format, product line, audience, or stage of the buying cycle. A site publishing news, evergreen guides, case studies and documentation has four groups that will still make sense in three years.
One rule saves an argument later. Keep the label set fixed and documented outside GA4, in the same file as the event taxonomy. Two people adding groups in different months will invent "blog", "Blog" and "blog posts" if nobody wrote the list down.
Extra levels cost quota
The second and third levels are custom dimensions, and those are finite. A standard property allows 50 event-scoped custom dimensions, a 360 property 125. User-scoped dimensions are 25 and 100, item-scoped 10 and 25, custom metrics 50 and 125. Registration is not instant either. Google states that after the parameter is sent and the dimension created, 24 to 48 hours pass before you can report on it.
Two cautions. Registering a custom dimension for a parameter that is already predefined, such as the page dimensions or transaction ID, does not change cardinality and still spends quota. And any value in a custom dimension that looks like a number is treated as one, which turns a group code such as "94E40" into 9.4e+41 in reports. Use words for labels.
Where content groups break
Five failures account for most broken content group reports.
(not set) at the top. The parameter was missing from the event that produced the row, or the tag that fired was not the config tag. Check one URL per group in DebugView and confirm the value on the page_view event itself, rather than confirming that the variable resolves in GTM's variable panel.
Route changes on a single-page site. Enhanced measurement fires page_view "each time the page loads or the browser history state is changed by the active site", listening for pushState, popState and replaceState. A content_group set once in the config at load will not follow a route change by itself. If the URL updates without a reload, fire a config update per route or set the parameter from the data layer per view, and test a route change before trusting the numbers.
Duplicate labels. "guides" and "Guides" are two rows. Trim and lowercase in the variable, not in the report.
Cardinality. A group built from a free-text field, such as an author name or a tag list, can grow without limit. Where a dimension exceeds what the reporting tables hold, results collapse into an (other) row, and sampling begins at 10 million events for a standard property and 100 million for 360. Cardinality is a design constraint, not a formatting problem.
One template only. If the parameter sits in one of five page templates, four report (not set). Ship it at the tag level wherever the tag is shared.
Reading depth, which GA4 measures once
Views tell you a page was opened. Enhanced measurement adds one signal about whether it was read. The scroll event fires "the first time a user reaches the bottom of each page (i.e., when a 90% vertical depth becomes visible)", and it populates the Percent scrolled dimension, whose value in that case is 90.
Three things follow. It is a single threshold rather than a curve. It fires once per page per user rather than on every scroll. And 90 percent of a 300 word note arrives within two seconds, so it measures patience with the page length as much as interest in the words.
For the curve, build it. GTM has a Scroll Depth trigger that fires at the vertical percentages you list, so 25, 50, 75 and 90 can each send a GA4 event carrying a depth parameter. That buys one custom dimension and a distribution, plus a volume decision, because a long article can then produce four events per reader and those count toward every event total on the property.
Either way, compare completion within a length band. A 400 word news item and a 3,000 word guide are not in the same competition, and treating them as one makes the news look beloved.
The section view worth building
With the group in place the report gets short. One exploration, Content group as the first dimension, and four columns: Views, Active users, Scroll events, and a ratio of scroll events to views. Call it completion if you like, but remember it means "reached 90 percent", not "read every word".
Read the columns in pairs. High views with low completion is usually a length or opening problem, because people arrived and left before the end. Low views with high completion is a distribution problem, because everyone who found it stayed. Both are actionable, and neither shows up in a URL table.
Then narrow by acquisition. Add Session default channel group as a second dimension and one section can read weak from search and strong from email, which changes the fix.
Keep the list short enough to read in a meeting. Six to ten groups is a working report. Forty is a spreadsheet with a chart on top.
Three numbers that will mislead you
Views first. Because page_view also fires on browser history changes, a site that updates the URL without a reload can report more views than page loads, and an infinite scroll feed can report several views per visit. Views count view events, not pages opened.
Scroll second, for the reason already given. It is a threshold, and it flatters short content.
Consent third. With analytics_storage denied, GA4 sets and reads no analytics cookies, sends cookieless pings, and uses those pings for modelling. Your denominator is therefore not a count of everyone who visited but what could be measured plus what the model could infer. Section comparisons survive that, because every section loses the same kind of data. Absolute numbers do not, and a year-on-year chart that ignores a consent change measures the banner rather than the content. The ICO's April 2026 guidance on storage and access technologies keeps the position that nothing non-essential runs before consent, so the banner is a legal decision rather than an analytics preference.
What to do this week
- Write down the sections you report on, by name, in the measurement file.
- Pick one level for now, and decide whether a second earns one of your 50 custom dimensions.
- Map paths or CMS terms to those names in a RegEx Table, with the two advanced checkboxes cleared.
- Add
content_groupto the config tag and publish the container. - Check one URL per group in DebugView on the page_view event, including a route change if the site is single-page.
- Build the exploration with Views, Active users, Scroll events and the ratio, and send it to whoever asks the section question.
The parameter costs an afternoon and needs no rebuild. What it buys is a report that compares like with like, month after month. Most content sites already have the data. Far fewer have it arranged in a way that answers a question about the work.
Do Your Reports Compare the Right Things?
North Digital builds GA4 and GTM reporting that compares sections rather than individual URLs, so the monthly report answers the same question every month. The setup is documented, so the next analyst can pick it up cold.
Talk to North Digital