The best GA4 setup for multilingual websites
A website in three languages raises the same question every time: one GA4 property per language, or one for all of them? The answer is almost always one property, with the language added as a dimension. Separate properties triple the setup and maintenance (every key event, audience, link to Google Ads and custom dimension three times over) and make the most common question, "how does the whole site do?", impossible to answer without adding up reports by hand.
This guide covers the three ways multilingual sites are built, the GA4 setup for each, and how to add a page_language dimension in Google Tag Manager so you can still filter and compare per language.
Three ways to build a multilingual site
- Subfolders:
example.com/nl/,example.com/fr/. Most common, and the easiest to measure. - Subdomains:
nl.example.com,fr.example.com. - Separate domains:
example.nl,example.fr. Common for country stores, and the one that needs extra work.
All three end in the same place: one GA4 property, one web data stream, one Google tag, and a language dimension. What differs is how the visitor's identity survives a move between languages.
Subfolders and subdomains: one property, nothing extra
With subfolders everything runs on one domain, so one Tag Manager container loading one Google tag is the whole setup. Subdomains are just as simple: GA4 sets its cookie on the root domain, so a visitor moving from nl.example.com to fr.example.com keeps the same client ID without any configuration. (Universal Analytics needed cross-domain settings for this; GA4 does not.)
You can use one container across all subdomains or one per subdomain, as long as each loads the same measurement ID. One container is easier to keep consistent.
Separate domains: one property plus cross-domain tracking
Cookies are tied to a domain, so a visitor who goes from example.nl to example.fr would start a new session as a new user, with the first domain as referrer. Using the same measurement ID on both is necessary but not enough. Add both domains to the data stream:
- In GA4, go to Admin › Data collection and modification › Data streams and open your web stream.
- Click Configure tag settings, then Configure your domains.
- Add a condition for each domain, match type Contains:
example.nl,example.fr, and so on. Save.
The Google tag now adds a _gl parameter to links between those domains, carrying the client ID across, and GA4 stops treating them as referrals to each other. The full background, including redirects and forms that drop the parameter, is in cross-domain tracking between two different root domains.
Why GA4's own Language dimension is not enough
GA4 already has a dimension called Language, and it is tempting to use it. It holds the visitor's browser language, not the language of the page they read. A Dutch speaker with an English browser reading your Dutch pages counts as English, and an expat browsing your French site from a German laptop counts as German. For "how do my language versions perform" you need the page's language, which you add yourself as page_language.
Building page_language in Tag Manager
Pick the most reliable source on your site:
- The lang attribute (recommended when your CMS sets it correctly): every properly built page says its language in
<html lang="nl">. Create a Custom JavaScript variable namedPage languagewithfunction () { return (document.documentElement.lang || '').slice(0, 2).toLowerCase() || '(not set)'; }. It works for subfolders, subdomains and domains alike. - A RegEx Table on the URL, when the lang attribute is missing or wrong. Input
{{Page Path}}for subfolders (pattern^/nl/outputsnl) or{{Page Hostname}}for subdomains and domains (pattern\.nl$outputsnl). Uncheck Full Matches Only, and set a default value: your main language for a site whose default lives at the root, or(not set)to spot pages that slip through.
Use language codes (nl, fr, en) rather than names. They are shorter, never get translated by accident, and match what most CMSs, feeds and ad platforms use.
Sending it with every event and registering it
- Add a row to your Google Tag: Event Settings variable: parameter
page_language, value{{Page language}}. If you set up content groups, this is the same variable. - Make sure that variable is selected under Shared event settings in the Google tag and under Event Settings Variable in every GA4 event tag.
- In GA4, go to Admin › Data display › Custom definitions › Create custom dimension: name
Page language, scope Event, event parameterpage_language. Spell it exactly as in Tag Manager.
Register the dimension before you publish the container: custom dimensions only fill from the moment they exist. Then test in Tag Manager Preview and GA4 DebugView on a page in each language.
Content groups that work across languages
With the language in its own dimension, your content groups should leave it out. Group /nl/blog/, /fr/blog/ and /blog/ all as Blog (a pattern such as /blog/ without an anchor matches all three), and you can compare the blog across languages, or every section within one language, with two dimensions instead of a long list of combined groups. Translated slugs need one row per language: /blog/|/blogue/.
Ecommerce, currencies and Google Ads
- Currency: send the
currencyof each store with every ecommerce event. GA4 converts everything into the property's reporting currency, so revenue across country stores adds up correctly. - Key events: mark them once in the shared property. A purchase is a purchase in every language.
- Google Ads: one property linked once covers every language. Split campaigns by language in Ads, and use
page_languageto build audiences per language in GA4.
When you run GA4 through a server container
The setup above is the same: page_language and content_group are set in the browser and reach GA4 through the server container unchanged. Two things to align. With separate domains, every domain needs its own first-party tagging host, and the cross-domain settings above still apply in the data stream. And with one server container behind all languages, you can use page_language in server-side triggers, for example to send Dutch and French purchases to different Meta pixels. In Measurebase, one domain covers its subdomains; separate country domains are separate domains on the same account, see running multiple domains on one account.
Frequently asked questions
Should I ever use a separate GA4 property per language?
Only when the language versions are really separate businesses: different owners, teams that must not see each other's data, or legal reasons. For one company with one site in several languages, one property is easier to run and gives you totals without extra work.
Do I need a separate data stream per language?
No. One web data stream handles every domain, subdomain and subfolder. Multiple web streams in one property split your configuration (enhanced measurement, domains, internal traffic) without adding anything you cannot get from page_language.
What about a site where the default language has no prefix?
Then the RegEx Table needs the default value to be that language, since its pages match no pattern. The lang attribute avoids this entirely, which is one more reason to prefer it.
Can I see which language a visitor switched to?
Yes. Track clicks on your language switcher as an event (for example language_switch with from and to parameters), or compare page_language of the landing page with later pages in an exploration.
Does hreflang matter for GA4?
No. hreflang tells search engines which page serves which language; GA4 ignores it. It is a good sign that your lang attributes are set correctly too, which the recommended variable relies on.