GA4 Analytics skill for Codex

Events that fire once and match your backend. Codex implements GA4 from a tracking plan rather than ad hoc snippets: consent defaults before any tag, recommended event names and ecommerce parameters, outcome-based triggers, key events and custom dimensions registered in admin. It hunts down double installs and duplicate SPA page views, sends server-only events through the Measurement Protocol, and reconciles purchase counts with the order database.

  • Growth & SEO
  • $ga4-analytics
  • 205 lines
  • 2 files
  • ~2,250 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- ga4-analytics

Installs to ~/.agents/skills/ga4-analytics/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $ga4-analytics, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Implements and audits Google Analytics 4 — gtag.js or Google Tag Manager dataLayer events, recommended event names and parameters, ecommerce purchase tracking, key events, custom dimensions, Consent Mode v2, SPA page views, cross-domain and referral exclusions, Measurement Protocol for server events, DebugView verification, and BigQuery export. Use when adding GA4 to a site or app, events are missing or duplicated, conversions do not match the backend, consent banners need wiring, or someone asks for a GA4 report or exploration.

Use it when

  • Installing GA4 or migrating tracking to Google Tag Manager
  • Events or conversions are missing, duplicated or do not match the backend
  • Wiring a consent banner to Consent Mode v2
  • Server-side events need to reach GA4

Use something else when

How to check the result

Walk every tracking-plan row in DebugView, confirm consent denial sets no _ga cookie, and reconcile seven days of purchases against the order database.

The mistake it prevents

Sending manual page_view events on SPA route changes while enhanced measurement already tracks history changes, doubling every page view.

~/.agents/skills/ga4-analytics/SKILL.md

--- name: ga4-analytics description: Implements and audits Google Analytics 4 — gtag.js or Google Tag Manager dataLayer events, recommended event names and parameters, ecommerce purchase… license: MIT ---

Done well: every event in GA4 maps to a line in a written tracking plan, uses recommended names where they exist, fires exactly once per user action, respects the user’s consent choice, and key event counts reconcile with the backend within an agreed tolerance.

Before writing code

  • Read AGENTS.md, then find existing tracking: grep -rnE "gtag\(|dataLayer|googletagmanager|G-[A-Z0-9]{6,}|GTM-[A-Z0-9]+" --exclude-dir=node_modules . Two tag installs (hard-coded gtag plus GTM, or GA4 via a CMS plugin plus code) is the most common cause of doubled page views.
  • Find the consent management platform (CMP) and how it exposes choices.
  • Decide gtag.js vs GTM: GTM if marketers manage tags; gtag.js directly if engineering owns everything. In GTM, the code only pushes to dataLayer; tags live in the container.
  • Get or write the tracking plan (event taxonomy design belongs to analytics-tracking-plan). Each row: event name, trigger, parameters with types, key event yes/no, owner.

Workflow

- [ ] 1. Base install with Consent Mode v2 defaults before any tag
- [ ] 2. Page views (correct for SPAs)
- [ ] 3. Events from the tracking plan, recommended names first
- [ ] 4. Ecommerce and key events
- [ ] 5. Register custom dimensions; admin settings
- [ ] 6. Server-side events via Measurement Protocol (if needed)
- [ ] 7. Verify in DebugView and reconcile with backend

Consent defaults must run before the GA tag loads:

<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('consent', 'default', {
    ad_storage: 'denied',
    ad_user_data: 'denied',
    ad_personalization: 'denied',
    analytics_storage: 'denied',
    wait_for_update: 500
  });
</script>
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
<script>
  gtag('js', new Date());
  gtag('config', 'G-XXXXXXXXXX');
</script>

When the user chooses, the CMP (or your code) calls:

gtag('consent', 'update', { analytics_storage: 'granted', ad_storage: 'denied',
  ad_user_data: 'denied', ad_personalization: 'denied' });
  • Consent Mode v2 (ad_user_data, ad_personalization) is required by Google for ads features with EEA/UK users. Defaults can differ by region with the region key.
  • Basic mode: tags do not load until consent. Advanced mode: tags load and send cookieless pings when denied, enabling behavioral modeling once traffic thresholds are met. Which mode is lawful is a legal decision for the site owner; implement what they choose and do not decide it for them.
  • Persist the choice and re-apply update on every page load before events fire.

2. Page views

  • Enhanced measurement sends page_view on load and, if “page changes based on browser history events” is enabled, on SPA route changes. Do not also send manual page_view on route change, or every view counts twice.
  • If you disable history-based tracking, send manually after the new title is set:
gtag('event', 'page_view', { page_location: location.href, page_title: document.title });

3. Events

Use recommended event names and parameters where they fit; GA4 reports and integrations understand them:

ActionEventKey parameters
Account createdsign_upmethod
Logged inloginmethod
Lead form submittedgenerate_leadvalue, currency, lead_source (custom)
Site searchsearchsearch_term
Product viewedview_itemcurrency, value, items
Added to cartadd_to_cartcurrency, value, items
Checkout startedbegin_checkoutcurrency, value, items, coupon
Purchasepurchasetransaction_id, value, currency, tax, shipping, items

Custom events: snake_case, verb_object (invite_teammate, export_report). Limits: names ≤ 40 characters starting with a letter; ≤ 25 parameters per event; parameter names ≤ 40 characters and values ≤ 100 characters; reserved prefixes google_, ga_, firebase_ are not allowed. Never send PII (emails, names, phone numbers) in any event, parameter or URL; it breaks Google’s terms and privacy law.

Fire on the confirmed outcome, not the click: sign_up after the API returns success, not when the button is pressed.

With GTM, push to the dataLayer and configure GA4 event tags on custom event triggers:

window.dataLayer.push({ ecommerce: null }); // clear previous ecommerce object
window.dataLayer.push({
  event: 'purchase',
  ecommerce: {
    transaction_id: order.id, value: order.total, currency: order.currency,
    tax: order.tax, shipping: order.shipping,
    items: order.lines.map(l => ({ item_id: l.sku, item_name: l.name, price: l.unitPrice, quantity: l.qty }))
  }
});

4. Ecommerce and key events

  • purchase needs a unique transaction_id; GA4 deduplicates purchases with the same ID for the same user, which protects against thank-you page reloads only if the ID is always sent.
  • value is a number (no currency symbol), currency an ISO 4217 code; both required for revenue.
  • Mark business outcomes as key events in Admin → Events (key events replaced “conversions” in GA4 in 2024; Google Ads still imports them as conversions).

5. Admin settings that matter

  • Data retention: set to 14 months (default is 2) for explorations; not retroactive.
  • Register custom parameters as custom dimensions/metrics (Admin → Custom definitions) or they will not appear in reports; also not retroactive. Standard properties allow 50 event-scoped and 25 user-scoped custom dimensions.
  • Define internal traffic and activate the filter; list unwanted referrals (payment providers, auth domains) so sessions are not restarted as referrals.
  • Cross-domain measurement for multi-domain journeys (Admin → Data streams → Configure tag settings → Configure your domains).
  • Custom channel group for AI assistants (source matches a regex such as chatgpt|perplexity|gemini|copilot, extended as new assistants appear) so they do not sit in Referral.
  • Link BigQuery export for raw event access (free, daily export limits apply to standard properties).

6. Measurement Protocol for server events

For events only the server knows (subscription renewals, refunds, offline conversions):

curl -s -X POST "https://www.google-analytics.com/mp/collect?measurement_id=G-XXXXXXXXXX&api_secret=$GA_API_SECRET" \
  -H 'Content-Type: application/json' \
  -d '{"client_id":"123456.7654321","events":[{"name":"purchase","params":{"transaction_id":"R-1001","value":49.0,"currency":"USD","session_id":"1730000000","engagement_time_msec":1}}]}'
  • client_id must be the browser’s GA client ID (from the _ga cookie, stored with the order) to join the user’s journey; user_id if you use User-ID. Respect consent: do not send for users who denied analytics.
  • Validate first against https://www.google-analytics.com/debug/mp/collect, which returns validation messages (the real endpoint always returns 2xx). Keep the API secret server-side. Network access is required; ask before sending to a production property.

7. Verify

  • DebugView: enable with gtag('config', 'G-XXXXXXXXXX', { debug_mode: true }) on a dev build, the Tag Assistant / GTM Preview, or the GA debugger extension. Walk each tracking-plan row and confirm event name, parameters and that it fires once.
  • Browser devtools: filter network by collect?v=2; check en= (event name) and gcs=/gcd= (consent state) values.
  • Test consent: with denial, no _ga cookie is set; after granting, events and cookies appear.
  • Reconcile: compare GA4 purchase count and revenue with the order database for the same days and timezone (property timezone). 5–10% under-count is normal with ad blockers and consent denial; over-count means duplicates.

Gotchas

  • Reports have processing delays of up to about a day; Realtime and DebugView are not the same as finished reports.
  • Thresholding hides rows with small user counts when Google signals is on; switch reporting identity to device-based to inspect.
  • (not set) landing pages often come from sessions started by events without a page_view, e.g. Measurement Protocol events without session_id.
  • UTM-less links in emails and apps land in Direct or Unassigned; see utm-attribution.
  • A SPA that changes the URL before updating document.title records the previous page’s title.

Output

**Install:** gtag via app/layout.tsx; duplicate WordPress plugin tag removed.
**Consent:** v2 defaults denied (EEA+UK region), CMP updates via onConsentChanged.
**Events:** 11 plan rows implemented (table with name, trigger file:line, params).
**Key events:** sign_up, generate_lead, purchase. Custom dims: plan_tier, lead_source.
**Verified:** DebugView walkthrough of all 11; purchases 7 days: GA4 412 vs DB 441 (-6.6%).
**Open:** server-side refund events via Measurement Protocol (needs API secret).

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "GA4 Analytics"
  short_description: "Events that fire once and match your backend"
  default_prompt: "$ga4-analytics GA4 shows twice as many page views as our server logs, find out why"

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
Just launched

Codex Skills Pro is live

79 premium skills for Codex, each with scripts, references and an evidence report that proves the result.

Bought one by one$882.21
All 79 skills$49
94% off

That's $0.62 a skill, paid once.

Get all 79 for $49Or pick single skills from $4.99 →

Secure checkout by Creem