Server-side GTM cookie and FPID reference

last verified · against GA4 cookie and server-side identification docs as of 2026-07

Reference for server-side GTM FPID cookies: JavaScript Managed versus Server Managed client identification, the _ga, FPID, and FPLC cookies, and migration.

What this is

A lookup for the cookies that carry GA4 identity across a server-side Tag Manager setup: the JavaScript-set _ga and _ga_<container-id> cookies, the server-set FPID identifier, and the FPLC cross-domain linker. It records which cookie each client-identification mode uses, how the two modes are selected, and the browser lifetime caps that motivate moving identity to the server.

Cookie Purpose (per docs) Set by Readable by Default lifetime
_ga “Used to distinguish users.” Stores the GA4 client ID On-page gtag, via document.cookie Browser JavaScript 2 years (configured)
_ga_<container-id> “Used to persist session state.” On-page gtag, via document.cookie Browser JavaScript 2 years
FPID First Party Identifier; its value sets the outgoing client_id Server container, via Set-Cookie header Server only (HttpOnly) 2 years (practitioner-sourced)
FPLC Cross-domain linker; value hashed from FPID Server container / GA4 client Browser JavaScript (not HttpOnly) 20 hours

The support page documents _ga and _ga_<container-id> by name, purpose, and 2-year default. The cookie name FPID, its HttpOnly flag, and its exact Max-Age are not stated on the official Google pages read here; those specifics are corroborated by practitioner sources (Simo Ahava, InfoTrust, iDimension) and are flagged as officially under-documented. The FPID value is a hash that incorporates a server-side seed, so it cannot be deduced from _ga with client-side code. Internal cookie value formats are not vendor-documented; treat any described layout as reverse-engineered.

Client identification modes

The Cookies and client identification setting on the built-in Google Analytics: GA4 server client selects which cookie supplies the outgoing client_id.

Mode Identifier source Cookie Cookie readable by ITP / JS-cookie truncation
JavaScript Managed Incoming &cid parameter _ga Browser JavaScript Subject to browser caps
Server Managed FPID cookie, preferred over incoming &cid FPID Server only (HttpOnly) Not truncated the way a JS-set cookie is
  • JavaScript Managed is the default and the only method that works until the server container’s domain settings are changed.
  • Server Managed writes a first-party FPID cookie by default and parses the identifier from it, preferring the FPID-derived value over the incoming &cid.
  • The server client’s mode governs which value the server uses as client_id, not whether on-page gtag writes _ga / _ga_<container-id>; gtag still manages those client-side cookies regardless of the mode. This interaction is an inference, not officially confirmed.
  • Durable, HttpOnly FPID only materializes when the server container is served from a same-site first-party subdomain. On the default vendor domain, FPID loses its first-party durability (domain and transport wiring is covered in the first-party transport reference and not duplicated here).

Options passed to the on-page Google tag that shape the _ga cookies.

Field Effect Notes
cookie_expires Cookie lifetime in seconds 2419200 = 28 days; 0 = session cookie that expires on browser close
cookie_prefix Prepends a prefix, so _ga becomes <prefix>_ga Changing the name orphans existing cookies (identity reset)
cookie_update Boolean; default true The cited source lists it without describing behavior
cookie_domain Domain the cookie is scoped to Defaults to the highest possible level (blog.example.com scopes to example.com); localhost uses 'none'

Cookie expiry can be reconfigured in Analytics settings. GA identity is keyed to the exact cookie name, so changing cookie_prefix (or otherwise renaming the cookie) makes returning visitors appear as new users.

Browsers cap the lifespan of first-party cookies when a user does not return, independently of GA4’s configured 2-year expiry.

Browser Cap on first-party cookie lifespan
Chrome 400 days maximum
Safari 7 days

The Safari cap applies to JavaScript-set (document.cookie) cookies such as _ga. A server-set HttpOnly FPID avoids that ITP truncation and improves returning-user durability. How the 400-day and ITP caps apply to a server-set HttpOnly cookie in practice, and whether FPID inherits cookie_expires or a fixed Max-Age, are not confirmed on an official page.

Migration: JavaScript Managed to Server Managed

The Migrate from JavaScript Managed Client ID option retrofits server-managed identification onto a live property without a one-time inflation of new-user counts.

Population Behavior
Existing users (have _ga) Keep the _ga client_id until that _ga cookie is deleted or the Client ID is reset, then move to FPID
Brand-new users Get FPID directly
During the transition Both the client-side (_ga) and server-side (FPID) IDs can appear for events, visible in the BigQuery export

New deployments can enable Server Managed directly. The transition period resolves as _ga cookies age out; plan identity-dependent analyses (LTV, cohorts) around the migration window.

Enabling and disabling FPID (the Name field)

Name field value Effect
FPID (or the intended name) Client writes FPID via Set-Cookie and reads it to generate the outgoing client_id
Blank / empty string Client neither writes nor reads FPID; identification falls back to the incoming &cid / _ga

Blanking the Name field is the documented way to intentionally prevent the server container from setting FPID (for example, to honor a consent state or keep a pure client-side _ga setup). The full set of server-managed cookie settings fields (Domain, Path, SameSite, Secure, Max-Age) beyond Name was not confirmed on any page read here; treat that field list and its defaults as unverified. SameSite / Secure defaults of the JavaScript-set _ga cookies were also not confirmable and are flagged as unverified.

Cross-domain identification (FPLC)

Because FPID is HttpOnly and unreadable by the JavaScript cross-domain linker, the GA4 client sets a companion non-HttpOnly FPLC cookie for cross-domain stitching.

Item Value
Cookie FPLC (cross-domain linker)
HttpOnly No (JavaScript-readable)
Value Hashed from the FPID value
Lifetime 20 hours
Requirement Source and target server containers must share the same GTM account

If a user stays on a page longer than 20 hours before clicking a cross-domain link, the linker value is stale and stitching can fail. FPLC is a session-scoped bridge for cross-domain tracking, not a durable identifier.

Gotchas

  1. Returning Safari / iOS visitors are counted as new; user and session counts inflate. Cause: _ga is JavaScript-set via document.cookie and Safari ITP caps client-script-set first-party cookies to about 7 days, so the client_id is lost between visits. Fix: switch the GA4 client to Server Managed so the identifier lives in the HttpOnly, server-set FPID cookie (requires a genuine same-site first-party subdomain — see the first-party transport reference).
  2. Switching an existing site to Server Managed spikes “new users” and duplicates users. Cause: the FPID-derived client_id differs from the pre-existing _ga client_id, so established users are re-identified as new. Fix: enable Migrate from JavaScript Managed Client ID so returning users keep _ga until it is deleted or reset, then move to FPID; only new users get FPID immediately.
  3. Cross-domain tracking stops stitching after enabling FPID / Server Managed. Cause: FPID is HttpOnly and unreadable by the JS linker, and/or the FPLC cookie (20-hour lifetime) is missing or stale, or the two containers are in different GTM accounts. Fix: ensure the non-HttpOnly FPLC is present, the click is within its 20-hour window, and both server containers belong to the same GTM account.
  4. The same person shows two different client_id values during migration (for example in BigQuery). Cause: expected behavior — the client-side _ga ID and the new server-side FPID ID coexist until the old _ga cookie expires or is deleted. Fix: treat as transitional; it resolves as _ga cookies age out. Plan identity-dependent analyses around the window.
  5. FPID is never written and identification silently falls back to &cid / _ga. Cause: the Name field in the server-managed cookie settings is an empty / blank string, which disables both writing and reading FPID. Fix: set the Name field to FPID (or the intended name); conversely, blank it deliberately to prevent FPID for a consent gate.
  6. Setting a cookie_prefix or renaming GA cookies makes all users appear new. Cause: GA identity is keyed to the exact cookie name; changing the name / prefix orphans existing cookies. Fix: do not change the cookie name / prefix after launch; if you must, expect an identity reset and plan for it.
  7. FPID is set but behaves like a third-party cookie or has a short lifetime. Cause: the server container is served from a non-same-site host (the default vendor domain) rather than a first-party subdomain of the site. Fix: map the server container to a first-party subdomain (covered in the first-party transport reference — not duplicated here).

Quick recipes

Customize the JavaScript-set GA4 cookies from the on-page tag:

ga4-cookie-config.js
// Configure _ga / _ga_<container-id> lifetime and scope.
gtag('config', 'G-XXXXXXX', {
cookie_expires: 2419200, // seconds; 2419200 = 28 days
cookie_domain: 'example.com', // default is the highest possible level
cookie_update: true // boolean, default true
});

Make _ga a session cookie that clears on browser close:

ga-session-cookie.js
gtag('config', 'G-XXXXXXX', {
cookie_expires: 0 // 0 = session cookie, expires on browser close
});

Read the JavaScript-accessible GA cookies from the browser (note FPID is absent because it is HttpOnly):

read-ga-cookies.js
// Split each cookie on its FIRST '=' so base64 values are not mangled.
const jar = Object.fromEntries(
document.cookie.split('; ').map((c) => {
const eq = c.indexOf('=');
return [c.slice(0, eq), c.slice(eq + 1)];
})
);
console.log('client id cookie:', jar['_ga']); // e.g. _ga
console.log('session state:', jar['_ga_XXXXXXXXXX']); // _ga_<container-id>
console.log('cross-domain linker:', jar['FPLC']); // present, 20h TTL
console.log('FPID visible to JS:', jar['FPID']); // undefined (HttpOnly)

Confirm the server sets FPID via the response header, and that it is HttpOnly (server-only):

inspect-fpid-set-cookie.sh
# The server container writes FPID in the Set-Cookie response header.
# Dump response headers from a GET (-D -) and discard the body (-o /dev/null).
curl -s -o /dev/null -D - "https://metrics.example.com/g/collect?v=2&tid=G-XXXXXXX" \
| grep -i 'set-cookie'
# Expect a Set-Cookie: FPID=...; HttpOnly line (FPID is not readable by document.cookie).

Avoid an accidental identity reset from renaming cookies:

cookie-prefix-warning.js
// A cookie_prefix renames _ga to <prefix>_ga. GA identity is keyed to the
// exact cookie name, so changing this AFTER launch orphans existing cookies
// and every returning visitor is counted as new. Do not change post-launch.
gtag('config', 'G-XXXXXXX', {
cookie_prefix: 'shop' // _ga becomes shop_ga (plan for the reset)
});

Sources

Official pages these values were read from (read 2026-07-20):

The FPID cookie name, its HttpOnly flag, the Set-Cookie delivery, the ~2-year lifetime, the FPLC linker (20-hour TTL), the blank-Name-field behavior, and the migration mechanics are corroborated by practitioner sources rather than an official Google page, and are flagged above for in-product verification (read 2026-07-20):

Changelog

  • — Initial version, verified against official documentation.

dataLayer

0 events · 0 sent

    • home /
      writing /writing
      guides /guides
      work /#work
      about /about
      colophon /colophon
      toggle analyst mode ctrl+.
      print session receipt /#receipt