17 September 2026 · 9 min read

Client ID and Session ID in GTM: The Supported Way to Read Them

Ask three analysts how to get the GA4 client ID into Google Tag Manager and you will get three custom JavaScript variables. Each one parses a cookie, splits a string on a period, and rebuilds a value Google has never promised to keep in that shape. When the cookie format moved inside the container cookie, the variables kept returning something. It was just the wrong something, and nothing in the container said so.

On 11 December 2025 Google shipped the supported path. Three built-in variables appeared in the Utilities category of Google Tag Manager: Analytics Client ID, Analytics Session ID, and Analytics Session Number. The same release added an Analytics Storage user-defined variable type for containers that need a specific measurement ID or a custom cookie prefix.

It reads like a minor release note. In practice it removes a category of fragile code from every container that carries it, and it changes what is reasonable to ask for in a measurement plan. Here is what each variable returns, where the built-in version stops being useful, and the part that still depends on consent.

What the three built-in variables return

Google's wording matters here, because the three are not symmetrical. Analytics Client ID returns the client ID from the default Google Analytics client ID cookie, which is the _ga cookie on a standard implementation. Analytics Session ID returns session IDs from all default prefixed Google Analytics session cookies. Analytics Session Number returns session numbers from those same cookies.

Read the session definitions again. If more than one Google Analytics session cookie is present, the variable returns a string value containing all relevant session IDs. Not the one you wanted. All of them, joined into a single value.

Multiple session cookies are more common than most people assume. A site running the Google tag alongside a container-managed session cookie, a domain with two GA4 properties collecting into different measurement IDs, or a migration that left an older cookie in place will each produce more than one candidate. In those cases the built-in variable hands you a compound value, and compound values do not join cleanly to anything downstream.

Turning them on takes a minute

Variables, then Configure next to Built-in Variables, then the Utilities group. Client ID, Session ID and Session Number sit in that list with Container ID, Event Name and Random Number. Tick the boxes. There is no code, no naming convention and no parsing to maintain.

What the interface does not tell you is which custom JavaScript variables elsewhere in the container were doing the same job before this release. Every one of those is now a duplicate at best and a conflict at worst, and none of them will fail when Google changes the cookie format again. Before deleting them, check which tags read them. A variable that only feeds a debug tag can go. A variable feeding an offline conversion tag needs to be swapped, not removed.

What the old parsed variable looked like

The client ID pattern was the friendlier of the two. It matched a cookie, pulled out a numeric pair and returned it:

function () {
  var match = document.cookie.match(/_ga=GA\d\.\d\.(\d+\.\d+)/);
  return match ? match[1] : undefined;
}

That works until Google adds a segment to the cookie, at which point you get an empty string on every hit and no error anywhere. Session IDs were worse, because the session cookie name carries the measurement ID, so a container serving more than one property needed a lookup table, a regex, and a written explanation for the next person who touched it.

Both patterns existed only because the values were locked inside cookie strings. Google's own release note for the template API that came first described the situation without softening it: developers relied on reverse-engineering or custom parsers of Google-set cookie formats, which broke whenever Analytics changed those formats.

When the built-in value is not enough

The Analytics Storage variable type is the second half of the release, and it exists for containers where a single value has to map to a single property. It offers three data fields and two optional settings.

Data field Measurement ID Cookie prefix What it returns
Client ID Not applicable Optional The pseudonymous identifier for the browser and device, filtered to one cookie prefix when configured.
Session ID Optional Optional With a measurement ID: the single session ID for that property. Without one: a compound string of IDs from all relevant GA session cookies.
Session Number Optional Optional Same behaviour. A single session number when a measurement ID is set, a compound string when it is not.

For a container that touches one property, the built-in variables are enough. For an agency container serving several properties, the measurement ID field is the difference between a value you can join and a value you have to parse. Parsing is exactly what this release was meant to end, so putting a compound string back into a Custom JavaScript variable defeats the point.

The cookie prefix covers renamed cookies, which turn up in server-side tagging setups, in some consent manager configurations, and on sites that carried a non-default prefix through a migration. Leave the prefix blank on a site that needs it and the variable returns empty. The tag still fires, the request still leaves the browser, and the parameter arrives empty. That is the quiet failure mode to test for.

The custom template route

Developers building custom templates got their version of the fix earlier, on 1 August 2025, with the readAnalyticsStorage sandbox API. It reads client and session IDs from inside a custom template rather than through a variable reference.

For most containers this is the wrong tool. A custom template is worth the maintenance only when the value is needed inside template logic itself, for example hashing an identifier before a request leaves the browser. Every other case is better served by the built-in variables, because they are configured rather than coded and they will not need an owner when the cookie format changes.

Consent decides whether the value exists at all

Every one of these variables reads a cookie. Under Consent Mode v2 with analytics storage denied, Google Analytics does not write those cookies, so there is no client ID and no session ID to read. The variable returns empty rather than throwing an error, and a tag that was supposed to stitch a server-side event to a browser session sends nothing to stitch with.

That has an order-of-operations consequence that shows up in the data rather than in Preview mode. A tag firing on gtm.js reads the cookie before the consent banner has resolved, so it collects empty values for visitors who later accept. By the time a second hit fires, the same visitor has an identifier and joins correctly. The result is a gap between the visitors your consent rate suggests you should be able to measure and the visitors you actually can.

On UK sites the practical fix is to read these values at the point where they are needed and a consent decision has been recorded, not on the earliest available trigger. That usually means the form submission, the checkout step, or the link click that matters, plus a consent gate that stops the tag from firing on denied visitors.

Client ID is not a user ID

This is where the excitement around the release needs tempering. The client ID is a pseudonymous identifier for a browser and device combination. It changes when cookies are cleared, it differs between a phone and a laptop for the same person, and it has no relationship to a CRM contact ID unless you create one. The session ID changes after the session timeout. The session number tells you where in the visiting sequence an event sits.

Sending a client ID to Google with the Measurement Protocol ties an offline event to a pseudonymous device, not to a named person. Combined with other information in your systems, that is personal data under UK GDPR, and the lawful basis for processing the offline event is yours to establish. Reading a client ID from a cookie is not consent, and it is not a replacement for it. If a client asks for offline conversions, the conversation about identifiers and the conversation about consent are the same conversation.

What agencies actually use these for

Offline conversion import is the clearest case. A lead that closes three weeks after the form submission has to reach Google with a client ID or a transaction ID so Ads can credit the campaign that produced it. The client ID must be captured at submission and stored beside the lead. Using a supported variable rather than a parsed cookie means that capture survives the next cookie format change, which is the whole point.

Joining GA4 to a CRM or a warehouse is the second. Client ID and session ID are the join keys between GA4 data and the system holding the lead record, whether that is a BigQuery export, a CRM, or a spreadsheet someone maintains by hand. Compound values break those joins without an error, which is the argument for the Analytics Storage variable with a measurement ID in every multi-property container.

Debugging disagreement is the third, and the one people forget. When two reports on the same traffic tell different stories, session ID and session number show whether you are looking at one session counted twice or two sessions counted once. In Preview mode that check takes a minute and saves an afternoon of arguing about whose number is right.

What to check in the container this week

  1. Open Variables and confirm which of the three built-in variables are enabled. If they are not, turn them on before anyone writes another custom parser.
  2. Search the container for custom JavaScript variables that read _ga or a session cookie. Each one is a maintenance liability and a candidate for replacement.
  3. If the container serves more than one GA4 property, create one Analytics Storage variable per property with the measurement ID set, and name each one after the property it reads.
  4. Test in Preview with consent granted and with consent denied. Empty when denied is correct. Empty when granted is a bug, usually a missing cookie prefix or a tag firing too early.
  5. Write down which variable feeds which tag. The next person to open the container should not have to reverse engineer it again.

What this does not fix

The release settles a parsing problem. It does not open the consent gate, it does not turn a client ID into a person, and it does not join a session across devices. Containers still need a decision about which identifiers leave the browser and where they are stored, and that decision belongs in the measurement plan rather than in the container.

The difference is that the identifier now comes from a supported variable with documented behaviour, which means a mistake is a configuration mistake instead of a rewrite. If identifier logic in a client container has been sitting on the to-do list because it looked like a development project, it is now a settings change on a Tuesday afternoon. Start with the container that causes the most arguments.

Not Sure What Your Container Reads?

North Digital audits GTM containers for UK agencies and brands: identifier logic, consent gating, and the joins between GA4 and the systems that hold your leads. You get a written list of what to fix and what it is worth.

Get a Free Tracking Audit