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:
mime-type:image/gif larger-than:1K domain:example.comFull 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 |
Consent Mode gcs parameter
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 headers → Select a folder → Allow |
| 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
- 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 underOther/Ping) rather than a resource-type button. - Query String Parameters look empty or truncated. Cause: GA4 sends larger hits as a
POST(oftennavigator.sendBeacon), placing data in the body, not the URL. Fix: read the Payload tab (Form Data,view source/view URL-encoded); filterFetch/XHRto catch POST beacons. domain:google-analytics.commisses some hits. Cause: EU/region traffic goes toregion1.google-analytics.comand other variants that an exact domain filter excludes. Fix: filter on the path substring —collectorurl:collect— or usedomain:*google-analytics.com.- No
_gacookie, or thecidyou send never matches a real user. Cause: you copied the entire_gavalue (GA1.1.x.y) as the client ID. Fix: use only the last two dot-segments (e.g.908899769.1600020018) ascid/client_id. - Cookies are missing and
gcsshowsG100. Cause:analytics_storage/ad_storageconsent is denied, so GA runs cookieless/modeled and sets no_ga— expected, not a bug. Fix: confirmgcsin the Payload;G111= full consent,G100= denied; grant consent to verify cookie behavior. - 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 nohas-overrides:yeson the request. - A Measurement Protocol POST returns
2xxbut nothing shows in reports. Cause:/mp/collectreturns2xxfor any well-formed HTTP request and never validates the payload; malformed events drop silently. Fix: send the identical payload to/debug/mp/collect, readvalidationMessages, and confirm in DebugView. - “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.
- 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.
- A parser that reads
session_idfrom 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 readsession_idfrom thesidparam on the collect request instead.
Quick recipes
Inspect the data layer before the GA4 tag reads it:
// 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):
// _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.1600020018Network filter strings that catch every GA4 hit:
collect # any GA4 hit, GET or POST beaconurl:collect method:POST # only POST beacons (data in the Payload body)domain:*google-analytics.com # include region1 / EU endpointshas-overrides:yes # requests intercepted by Local OverridesDecode a copied GET collect URL into its parameters:
// 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 msReplay a captured Measurement Protocol hit against the validator (Copy as cURL, then repoint at /debug):
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 validRelated
Sources
Official documentation these values were read from (read 2026-07-20):
- Inspect network activity (Network panel overview) (read 2026-07-20)
- Network features reference (read 2026-07-20)
- Override web content and HTTP response headers locally (Local Overrides) (read 2026-07-20)
- View, edit, and delete cookies (read 2026-07-20)
- Measurement Protocol (GA4) reference (read 2026-07-20)
- Validating events (GA4 Measurement Protocol) (read 2026-07-20)
- GA4 cookie usage — Analytics Help (read 2026-07-20)
- Data layer — Tag Platform / Tag Manager (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):
- GA4 events demystified — Thyngster (David Vallejo) (read 2026-07-20)
- GA4 Measurement Protocol CheatSheet — Thyngster (read 2026-07-20)
- What the ‘gcs’ parameter means — owntag (read 2026-07-20)
- Understanding GA4 cookies — the _ga cookie — Optimize Smart (read 2026-07-20)
- GA4 Measurement Protocol tutorial — Optimize Smart (read 2026-07-20)
- GA4 Measurement Protocol parameter reference — Medium (Mssvarma) (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.