Chrome DevTools for analytics debugging

last verified · against Chrome DevTools + GA4 collection docs as of 2026-07

Debug GA4 tags in Chrome DevTools: filter the Network panel for collect hits, read /g/collect params, inspect _ga cookies, and use Local Overrides to test fixes.

What this is

A lookup card for debugging Google Analytics 4 and tag-manager implementations directly in Chrome DevTools — the Network panel, the Application cookie store, Local Overrides, and the Console. It maps the DevTools filters, columns, and detail tabs to the GA4 collect request, its first-party cookies, and the Measurement Protocol validation endpoint, so a hit can be verified without deploying a change.

Scope note. Google does not publish a “debug GA4 in DevTools” guide. The vendor-supported debugging tools are GA4 DebugView and Google Tag Assistant / Tag Manager Preview; inspecting /g/collect in the Network panel is a community-standard practice, not a documented workflow. The client-side collect parameter names below (en, tid, cid, _et, ep.*, and the rest) are not in any official Google reference — they come from reverse-engineering and community documentation and can change without notice. They are flagged as unofficial where they appear.

Network panel: filter syntax

The Filter text box accepts three forms. Multiple property filters combine with a space; check Invert to reverse the whole match.

Input Matches
string any resource whose URL/name contains the string
-string negates / excludes (e.g. -main.css)
/regex/ regular expression (e.g. /\.[cj]s+$/)

Network panel: property filters

Property keywords documented with examples:

Keyword Filters on Example / notes
domain request host wildcard supported, e.g. domain:*.com
method HTTP verb method:POST (also GET)
scheme http vs https scheme:https
larger-than response size bytes or a k suffix, e.g. larger-than:1K
mime-type resource MIME type mime-type:image/gif
url full URL url:collect
has-overrides Local Overrides applied yes, no, content, or headers, e.g. has-overrides:content

Combining filters is additive:

combined-filter.txt
mime-type:image/gif larger-than:1K domain:example.com

Full keyword set (research does not elaborate all of these): domain, has-response-header, has-overrides, is, larger-than, method, mime-type, mixed-content, priority, resource-type, scheme, status-code, url, cookie-domain, cookie-name, cookie-path, cookie-value, set-cookie-domain, set-cookie-name, set-cookie-value, response-header-set-cookie.

Resource-type buttons (Ctrl/Cmd-click to select more than one): All, Fetch/XHR, JS, CSS, Img, Media, Font, Doc, WS, Wasm, Manifest, Other.

The More filters menu adds toggles: Hide data URLs, Hide extension URLs, Blocked response cookies, Blocked requests, and 3rd-party requests.

Network panel: controls and export

Control Effect
Preserve log Persists requests across page loads and navigations — essential to catch a hit that fires just before a navigation or redirect
Disable cache Emulates a first-time visitor; also required to see full original headers instead of a “Provisional headers are shown” warning
Stop recording network log Toggles recording (Ctrl/Cmd+E)

Right-click a request under Copy: Copy as cURL, Copy as fetch, Copy as fetch (Node.js), Copy as PowerShell. The whole log saves via Export HAR (sanitized) or Export HAR (with sensitive data) and reloads with Import HAR. The Search pane (Ctrl/Cmd+F) searches across the request headers, payloads, and responses of every logged request.

Reading a collect request

A GA4 hit may travel as a GET (parameters in the URL query string) or a POST (parameters in the request body, often via navigator.sendBeacon). Whether a given hit uses GET or POST is not officially documented and is driven by payload size and transport — check both the URL and the Payload/request body. The client-side path is /g/collect (or /collect).

Per-request detail tabs:

Tab What to read
Headers Request/response headers; a “Provisional headers are shown” warning means the response came from cache — enable Disable cache and reload
Payload Query String Parameters and Form Data; view source shows the raw payload, and view decoded / view URL-encoded switch how the values are displayed
Preview Formatted view of the response
Response Raw response body
Initiator What triggered the request: Parser, Redirect, Script (links to the source line), or Other
Timing Request timing phases
Cookies Cookies sent and received (columns below)

Request Cookies tab columns: Name, Value, Domain, Path, Expires, Size, HttpOnly, Secure, SameSite.

GA4 client-side collect parameters (unofficial)

Reverse-engineered from live payloads (Thyngster) and analytics-community blogs. Not documented by Google; subject to change.

Param Meaning
v Measurement Protocol version (2 for GA4)
tid Stream / Measurement ID the payload is sent to
cid GA4 Client ID for the user/device
en event name
_et engagement time in milliseconds (e.g. _et=8139 is about 8.1 seconds)
ep.<name> string event parameter (dimension)
epn.<name> numeric event parameter (metric)
up.<name> user property (string)
upn.<name> user property (number)
dl document location (page URL)
dr referrer
dt page title
sr screen resolution, width x height
ul browser language
sid session ID
_s hit counter for the current page load
_p a random number/hash generated per page load
gtm hash of the GTM/gtag version and configuration
_ss session_start trigger
_fv first_visit trigger
seg session-engaged trigger
_c=1 marks the event as a conversion
gcs Consent Mode state — see below

gcs encodes consent as the prefix G1 followed by two digits (0 or 1): the first digit after G1 is ad_storage, the second is analytics_storage. G100 = consent not granted, G111 = full consent. In server-side GTM it appears as x-ga-gcs. (Community-documented.)

GA4 first-party cookies

Cookie Default expiry Purpose
_ga 2 years Distinguish users
_ga_<container-id> 2 years Persist session state

Browsers cap cookie lifetime regardless of the 2-year setting: Chrome to about 400 days, Safari to about 7 days. Expiration is configurable under Admin → Data Streams → Google tag settings.

_ga value format GA1.1.<random>.<timestamp>:

Field Meaning
1 version (GA1)
2 number of domain components
3 random unique ID
4 first timestamp

The Client ID is fields 3 and 4 joined — the last two dot-segments only. For GA1.1.908899769.1600020018 the client ID is 908899769.1600020018, not the whole value.

Local Overrides

Mock remote resources locally, even without access to them — for example, serve a modified gtag/GTM config and watch the resulting collect params change.

Step Action
Set up Right-click a request → Override content or Override headersSelect a folderAllow
Manage Sources → Overrides, toggle Enable Local Overrides; a saved icon marks overridden files
List Right-click a request → Show all overrides
Headers Modified values show green; removed headers show red and crossed out; use Add header to add new ones; refresh to apply

Limitations: cannot override source-mapped files; DOM tree edits in Elements are not saved; CSS edited in the Styles pane is not saved when it comes from an HTML file (edit it in Sources instead); the cache is disabled while overrides are active.

Application panel: cookies

View cookies under Application → Storage → Cookies after selecting an origin.

Columns: Name, Value, Domain, Path, Expires / Max-Age, Size, HttpOnly, Secure, SameSite, Partition Key, Priority.

The Filter box here filters only by Name or Value and is case-insensitive; it cannot filter by other columns. Editing: double-click an empty row to Add; double-click a field to Edit (Size updates automatically); Delete selected removes one; Clear all removes all.

Measurement Protocol: replay and validate

Purpose URL
Production https://www.google-analytics.com/mp/collect
Validation https://www.google-analytics.com/debug/mp/collect
Validation (EU) https://region1.google-analytics.com/debug/mp/collect

Required query parameters: api_secret plus measurement_id (web) or firebase_app_id (Firebase). All data goes as an HTTPS POST; the JSON body must be under 130 kB. Body limits: up to 25 events per request (events[]), each event name 40 characters or fewer, up to 25 params per event. Body fields include client_id / app_instance_id, user_id, timestamp_micros, events[].name, events[].params, user_properties, and consent.

The production endpoint returns a 2xx status merely for a well-formed HTTP request — it does not validate the payload, so a 2xx does not mean the hit was accepted. The validation endpoint returns a validationMessages array; each message has fieldPath, description, and a validationCode (e.g. NAME_INVALID). An empty array means valid. Events sent to the validation server do not appear in reports, and it does not verify api_secret or firebase_app_id.

Gotchas

  1. The collect request is nowhere in the Network log. Cause: the hit fires right before a navigation, redirect, or form submit and the log clears on each page load. Fix: enable Preserve log first, and filter by collect (beacons can sort under Other/Ping) rather than a resource-type button.
  2. Query String Parameters look empty or truncated. Cause: GA4 sends larger hits as a POST (often navigator.sendBeacon), placing data in the body, not the URL. Fix: read the Payload tab (Form Data, view source / view URL-encoded); filter Fetch/XHR to catch POST beacons.
  3. domain:google-analytics.com misses some hits. Cause: EU/region traffic goes to region1.google-analytics.com and other variants that an exact domain filter excludes. Fix: filter on the path substring — collect or url:collect — or use domain:*google-analytics.com.
  4. No _ga cookie, or the cid you send never matches a real user. Cause: you copied the entire _ga value (GA1.1.x.y) as the client ID. Fix: use only the last two dot-segments (e.g. 908899769.1600020018) as cid / client_id.
  5. Cookies are missing and gcs shows G100. Cause: analytics_storage/ad_storage consent is denied, so GA runs cookieless/modeled and sets no _ga — expected, not a bug. Fix: confirm gcs in the Payload; G111 = full consent, G100 = denied; grant consent to verify cookie behavior.
  6. The collect request shows as (blocked), failed, or (canceled). Cause: an ad blocker or tracking protection blocked google-analytics.com, or DevTools request blocking / a leftover Local Override is intercepting it. Fix: check More filters → Blocked requests and the Network request blocking tab, use a clean profile, and confirm no has-overrides:yes on the request.
  7. A Measurement Protocol POST returns 2xx but nothing shows in reports. Cause: /mp/collect returns 2xx for any well-formed HTTP request and never validates the payload; malformed events drop silently. Fix: send the identical payload to /debug/mp/collect, read validationMessages, and confirm in DebugView.
  8. “Provisional headers are shown” and values look incomplete. Cause: the resource was served from cache (or is invalid), so DevTools never saw the real headers. Fix: enable Disable cache and reload.
  9. Local Overrides edits don’t take effect. Cause: the file is source-mapped, or was edited in a non-persisting pane (e.g. Styles for CSS inside an HTML file), or Enable Local Overrides is off. Fix: turn on Sources → Overrides → Enable Local Overrides, edit in Sources, and pick a non-source-mapped resource.
  10. A parser that reads session_id from the _ga_<id> cookie breaks. Cause: Google is transitioning the session cookie from position-based GS1 to key-value GS2 (with $ separators and letter prefixes), and the two coexist. Fix: detect the GS1 vs GS2 prefix and parse accordingly, or read session_id from the sid param on the collect request instead.

Quick recipes

Inspect the data layer before the GA4 tag reads it:

inspect-datalayer.js
// Paste in the DevTools Console.
window.dataLayer; // print the queued array
// Log every future push so you see the payload shape as it arrives:
const _push = window.dataLayer.push.bind(window.dataLayer);
window.dataLayer.push = (...a) => { console.log('dataLayer.push', ...a); return _push(...a); };

Extract the GA4 Client ID from the _ga cookie (last two segments only):

cid-from-ga.js
// _ga format: GA1.1.<random>.<timestamp>; client ID = the last two dot-segments.
const ga = (document.cookie.match(/_ga=([^;]+)/) || [])[1] || '';
console.log(ga.split('.').slice(-2).join('.')); // e.g. 908899769.1600020018

Network filter strings that catch every GA4 hit:

network-filters.txt
collect # any GA4 hit, GET or POST beacon
url:collect method:POST # only POST beacons (data in the Payload body)
domain:*google-analytics.com # include region1 / EU endpoints
has-overrides:yes # requests intercepted by Local Overrides

Decode a copied GET collect URL into its parameters:

decode-collect.js
// GET hits only; POST beacons carry the same fields in the request body — read the Payload tab.
const u = new URL('PASTE_COLLECT_URL_HERE');
for (const [k, v] of u.searchParams) console.log(k, '=', v);
// key fields (unofficial): en = event name, tid = Measurement ID, cid = Client ID, _et = engagement ms

Replay a captured Measurement Protocol hit against the validator (Copy as cURL, then repoint at /debug):

validate-hit.sh
curl -s "https://www.google-analytics.com/debug/mp/collect?measurement_id=G-XXXXXXX&api_secret=SECRET" \
-H "Content-Type: application/json" -d @payload.json
# an empty validationMessages array means the payload is valid

Sources

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

Community and reverse-engineered references for the unofficial client-side /g/collect parameters, the gcs encoding, and the _ga value format (read 2026-07-20):

Items the docs leave unstated are flagged inline rather than guessed: the exact GET-vs-POST transport threshold, the _ga_<id> GS1-vs-GS2 field positions, and the precise /debug/mp/collect path and web-vs-Firebase query-parameter pairing should be confirmed against the live documentation before scripting against them.

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