UNPKG

skills-ws

Version:

85 agent skills for AI coding assistants — marketing, growth, web3, dev, design & operations. Built for OpenClaw, Claude Code, Cursor, and Codex.

327 lines (255 loc) • 19.5 kB
--- name: google-analytics description: "GA4 implementation and analysis: event taxonomy, custom dimensions, key events (the 2024 rename of conversions), Google Ads import, GTM + Consent Mode v2, audiences, BigQuery export, and the Data API. Use when setting up GA4, fixing tracking/conversions, building Looker Studio reports, or querying analytics programmatically." --- # Google Analytics 4 ## Workflow ### 1. Measurement Plan Before touching GA4, define what matters: | Layer | Question | Example | |-------|----------|---------| | Business objective | What's the goal? | Increase trial signups 20% | | KPI | How do we measure? | Trial signup rate, activation rate | | Events | What do we track? | `sign_up`, `tutorial_complete`, `plan_selected` | | Dimensions | What context? | plan_type, referral_source, user_role | ### 2. Event Taxonomy Use a consistent naming convention. Never use spaces or capitals in event names. **Naming pattern:** `object_action` (noun_verb) ``` # Core events (auto-collected — don't recreate) page_view, session_start, first_visit, user_engagement # Recommended events (use GA4 standard names) sign_up, login, purchase, add_to_cart, begin_checkout # Custom events (your business logic) trial_started feature_activated plan_upgraded invite_sent onboarding_completed support_ticket_opened ``` **Implementation (gtag.js):** ```javascript // Custom event with parameters gtag('event', 'trial_started', { plan_type: 'pro', referral_source: 'pricing_page', value: 49 }); // User property (set once per user) gtag('set', 'user_properties', { account_type: 'enterprise', company_size: '50-200' }); ``` **GTM dataLayer push:** ```javascript dataLayer.push({ event: 'plan_upgraded', plan_from: 'free', plan_to: 'pro', mrr_delta: 49 }); ``` ### 3. GTM Implementation & Consent Mode v2 If you load GA4 through Google Tag Manager (web container), use exactly **one** GA4 base tag plus event tags — never also hardcode `gtag.js` for the same property (that is the #1 cause of doubled events). **Tag structure:** - **Google tag** (`G-XXXXXXX`) — fires once on *Initialization - All Pages* (formerly the "GA4 Configuration" tag; renamed to the unified *Google tag*). Set shared fields/user properties here. - **GA4 Event** tags — one per custom event, triggered by a *Custom Event* trigger matching your `dataLayer` `event` name. Map `dataLayer` values into **Event parameters** (these become GA4 event params; register them as custom definitions — section 4 — to use them in reports). - Pass enhanced-measurement-overlapping events carefully; disable GA4 enhanced measurement options you are sending manually to avoid duplicates (e.g. don't send a manual `page_view` if enhanced measurement page views are on). **Consent Mode v2 (required for EEA/UK ad/personalization features and Google Ads remarketing).** Set defaults *before* the Google tag fires (top of `<head>`, or a Consent Initialization trigger in GTM), then update on user choice: ```javascript // gtag — runs before any tag, defaults to denied window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('consent', 'default', { ad_storage: 'denied', ad_user_data: 'denied', // v2 signal ad_personalization: 'denied', // v2 signal analytics_storage: 'denied', wait_for_update: 500 // ms to wait for CMP before tags fire }); // After the user accepts in your CMP: gtag('consent', 'update', { ad_storage: 'granted', ad_user_data: 'granted', ad_personalization: 'granted', analytics_storage: 'granted' }); ``` With consent denied, GA4 uses **cookieless pings** (modeled/behavioral data) rather than dropping data entirely. The two v2 signals `ad_user_data` and `ad_personalization` are mandatory for EEA traffic to keep audiences/remarketing working in Google Ads. **Server-side GTM** (sGTM, runs in a Cloud Run / App Engine container) caveats: - Improves data quality and first-party cookie longevity, but does **not** make tracking consent-exempt — you still need a lawful basis and should forward consent state to the server container. - Set the GA4 client's `transport_url` to your sGTM endpoint; the server container's GA4 client claims the request and forwards to Measurement Protocol. - Watch for **duplicate events**: if both web GTM and sGTM send the same hit, or you mix `gtag` + sGTM, you double-count. Use a single send path per event. - Server-side dedup for purchase: include a `transaction_id`; GA4 dedupes `purchase` events with the same `transaction_id` within ~ the same session window. ### 4. Custom Dimensions & Metrics Register event parameters and user properties as custom definitions in **GA4 Admin → Custom definitions → Create custom dimensions/metrics**. Important nuance: GA4 *collects* event parameters as soon as you send them, but they are **not queryable as dimensions in standard reports / explorations until you register them** — and registration is **not retroactive** for the standard reporting surface (data flows into a registered dimension only from the time of registration onward). So register early. (BigQuery export and the Data API can access raw parameters without registration; see sections 11–12.) Limits: 50 event-scoped + 25 user-scoped + 50 custom metrics per standard property (more on GA4 360). | Scope | Dimension | Example values | Use | |-------|-----------|----------------|-----| | Event | plan_type | free, pro, enterprise | Segment by plan | | Event | feature_name | dashboard, export, api | Feature adoption | | User | account_type | individual, team, enterprise | User segmentation | | User | signup_source | organic, paid, referral | Acquisition quality | ### 5. Key Events (formerly "Conversions") **Terminology — get this right or you will miscommunicate with stakeholders.** In March 2024 Google renamed Analytics **conversions → "key events"**. The word *conversion* now means something different in each product: | Term | Where | Meaning | |------|-------|---------| | **Key event** | Google **Analytics** (GA4) | An important action you mark to measure (in reports/explorations). | | **Conversion** | Google **Ads** | A key event you have promoted for ad bidding/optimization. | Mark an event as a key event in **GA4 Admin → Data display → Key events → New key event** (or toggle the star on an existing event in the **Events** table). The old "Mark as conversion" toggle no longer exists; the metric in reports is now **Key events** (and `keyEvents` in the Data API — see section 12). **Primary key events (business-critical):** - `sign_up` — new account created - `purchase` — payment completed - `trial_started` — trial activated - `plan_upgraded` — expansion revenue **Micro key events (track for analysis; do NOT promote to Ads conversions / bid on):** - `onboarding_completed` - `feature_activated` - `invite_sent` #### Importing GA4 key events into Google Ads Marking an event as a key event in GA4 does **not** by itself make it an Ads conversion — that import is a separate, deliberate step (this is the most common 2026 setup failure): 1. Link the property in **GA4 Admin → Product links → Google Ads links** (enable personalized advertising / auto-tagging). 2. In **Google Ads → Goals → Conversions → Summary → New conversion action → Import → Google Analytics 4 properties**, select the key event(s) to import. 3. In Ads, each imported conversion has its own goal type (Primary = bids on it; Secondary = observe only) and its own attribution/count settings — set these in Ads, **not** GA4. Caveats (as of Jun 2026; verify at https://support.google.com/analytics/answer/13965727): - Importing a **non-key** event from GA4 automatically marks it as a key event in GA4. - Google periodically changes which auto-collected events count as key events by default; if your Ads bidding imports key events from GA4, re-verify your imported conversion list after any GA4 release and don't assume an event (e.g. `begin_checkout`) is still a key event without checking **Admin → Key events**. - Don't double-count: if you also run a Google Ads conversion tag (gtag/GTM) for the same action, set one of the two to Secondary or you will inflate conversions. ### 6. Audience Segments Build in **GA4 Admin → Data display → Audiences → New audience**. GA4 audiences are not a free-form SQL filter — they are composed from these exact constructs: - **Conditions** scoped *Across all sessions* / *Within the same session* / *Within the same event*, combined with AND/OR groups. - **Sequences** — ordered steps ("A then B"), each directly-followed-by or indirectly-followed-by, optionally time-constrained. - **Exclusions** — temporarily (while condition met) or permanently remove users. - **Membership duration** — 1–540 days (default 30); how long a user stays in the audience after qualifying. - **Metric thresholds** on event *count* and event *parameters* (e.g. `event_count for trial_started > 3`), and conditions on **registered** custom dimensions / user properties (section 4). Raw recency like "last active 30 days ago" is not a field — express recency with the **dynamic lookback** in the date scope, an **exclusion**, or a **predictive audience** instead. - **Predictive audiences** (require enough conversion volume to train): e.g. *Likely 7-day churning users*, *Likely 7-day purchasers*, *Predicted top spenders* — built on GA4's `churnProbability` / `purchaseProbability` metrics. Audiences populate from creation forward (mostly **not retroactive**). To use them for **remarketing**, the property must be linked to Google Ads (section 5) with personalized advertising enabled; otherwise they are analysis-only. | Audience | How to build it in GA4 (exact constructs) | Use | |----------|-------------------------------------------|-----| | Active trial users | Across all sessions: `trial_started` event in last 14 days (date scope) AND event_count of `session_start` ≥ 3 | Nurture campaigns | | Power users | `event_count` of `feature_activated` ≥ 10 (membership duration 30d) | Upsell targeting | | At-risk paying users | Predictive: *Likely 7-day churning users* AND user property `account_type = paid` | Win-back campaigns | | High-intent visitors | Sequence: `page_view` where `page_location` contains `/pricing` (≥ 2) → **exclude** users with a `sign_up` event | Retargeting ads | ### 7. Cross-Domain Tracking For multi-domain setups, configure linking in the UI (**GA4 Admin → Data streams → [web stream] → Configure tag settings → Configure your domains**) rather than hardcoding a linker — the UI writes the cross-domain config into the Google tag for all listed domains. Equivalent in code if you must: ```javascript gtag('config', 'G-XXXXXXX', { linker: { domains: ['example.com', 'app.example.com', 'checkout.example.com'] } }); ``` Also add the other domains to **Admin → Data settings → Data filters / unwanted referrals** so payment/auth domains (e.g. a Stripe checkout) don't start new sessions. Verify in GA4 DebugView — the same session/`session_id` should persist across domains and not restart. ### 8. Attribution Settings GA4 Admin → Attribution settings: - **Reporting attribution model:** Data-driven (default, and the only cross-channel model GA4 still offers — the last-click/position-based/linear "rules-based" models were removed in 2023–2024). - **Key-event lookback window:** acquisition key events default 30 days (configurable 7/30); all other key events default 90 days (configurable up to 90). - **Channel reporting:** uses the **default channel groups** (Cross-network, Paid Search, Organic Social, etc.); create a **custom channel group** if your UTMs don't map cleanly. ### 9. Looker Studio Reporting Connect GA4 as data source (the GA4 connector now exposes **Key events** and **Session key event rate**, not "Conversions"). Key dashboard pages: **Overview dashboard:** - Sessions, users, new users (line chart, 30d trend) - Session key event rate by channel (bar chart) - Top landing pages by sessions and key event rate (table) - Device category breakdown (pie chart) **Acquisition dashboard:** - Users by source/medium (table with sparklines) - Campaign performance (sessions, key events, cost per key event — blend with a Google Ads source for spend/CPA) - Organic vs paid trend (combo chart) **Engagement dashboard:** - Events per session by page (heatmap) - Feature adoption funnel (custom funnel chart) - User retention cohort (built-in cohort table) ### 10. Debugging **GA4 DebugView:** Enable with: ```javascript gtag('config', 'G-XXXXXXX', { debug_mode: true }); ``` Or install the **Google Analytics Debugger** Chrome extension, or use **GTM Preview** mode (which sends debug-flagged hits to DebugView). **Common issues:** - Events not showing → DebugView and Realtime are near-instant; standard reports lag (typically a few hours, up to 24–48h). If still missing, check the request fired (Network tab → `/g/collect`) and that an ad-blocker isn't dropping it. - Duplicate events → double install (GTM + hardcoded gtag), or both web GTM and server-side GTM sending the same hit; consolidate to one send path. - Key event not counting in Ads → confirm it's marked as a key event in GA4 **and** imported as a conversion in Google Ads (section 5); the two are separate steps. - Cross-domain breaks → check the configured-domains list and unwanted-referral exclusions (section 7); a restarted session usually means a referral wasn't excluded. - `(not set)` / `(other)` in reports → unregistered or high-cardinality custom dimensions, or params not sent on every event (section 4). ### 11. BigQuery Export (for serious analysis) For anything beyond the GA4 UI/Looker — funnel SQL, LTV, stitching to backend data — enable the free **BigQuery Linking** (GA4 Admin → Product links → BigQuery). Standard properties get **daily** export (and optional streaming/intraday); GA4 360 gets fresh-daily + streaming. Data lands in `analytics_<property_id>.events_YYYYMMDD` (+ `events_intraday_*` if streaming is on). **Schema essentials:** - One row per **event**. Event params live in the **`event_params`** `REPEATED RECORD` (key + `value.string_value` / `int_value` / `double_value`), so you must UNNEST to read a param. - `user_pseudo_id` = the device/client identifier (cookieless-ID friendly); `user_id` is your logged-in ID if you set it. - There is **no `session_id` column** — derive sessions from the `ga_session_id` event param (per `user_pseudo_id`). A session = (`user_pseudo_id`, `ga_session_id`). - Ecommerce items are in the **`items`** `REPEATED RECORD`; UNNEST to get per-item rows. - `event_timestamp` is **microseconds** (not millis) since epoch, UTC. **Pattern — pull one event param and count key events:** ```sql SELECT event_date, (SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'plan_type') AS plan_type, COUNT(*) AS event_count, COUNTIF(event_name = 'purchase') AS purchases, -- session count = distinct (user, ga_session_id) COUNT(DISTINCT CONCAT( user_pseudo_id, CAST((SELECT value.int_value FROM UNNEST(event_params) WHERE key='ga_session_id') AS STRING) )) AS sessions FROM `your_project.analytics_123456789.events_*` WHERE _TABLE_SUFFIX BETWEEN '20260501' AND '20260531' GROUP BY event_date, plan_type ORDER BY event_date; ``` **Common SQL pitfalls:** - **Always** filter on `_TABLE_SUFFIX` (the wildcard `events_*` scans every day = full-table cost). Combine intraday + daily carefully to avoid double-counting the current day. - A scalar subquery over `UNNEST(event_params)` returns the *first* match; if a key can repeat, aggregate instead. - GA4's UI session/engagement metrics won't match naive SQL exactly (the UI applies modeling, late-arriving hits, and its own session logic) — expect small deltas, define your metrics explicitly. - Revenue: use `ecommerce.purchase_revenue` (or sum `items.item_revenue`); don't sum a generic `value` param across event types. ### 12. GA4 Data API Query data programmatically (Python `google-analytics-data` library; `BetaAnalyticsDataClient` / `data_v1beta` is still the current GA4 reporting client as of Jun 2026 — verify at https://developers.google.com/analytics/devguides/reporting/data/v1). Authenticate with a service account: create one in Google Cloud, grant it **Viewer** on the GA4 property (Admin → Property access management), and point `GOOGLE_APPLICATION_CREDENTIALS` at its JSON key. ```python import os from google.analytics.data_v1beta import BetaAnalyticsDataClient from google.analytics.data_v1beta.types import ( RunReportRequest, DateRange, Dimension, Metric, ) # export GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json PROPERTY_ID = "123456789" # numeric property ID, not "G-XXXX" client = BetaAnalyticsDataClient() # picks up ADC from the env var above def fetch_all_rows(page_size: int = 100_000): """Paginate the report; GA4 returns max 100k rows per request.""" offset = 0 while True: req = RunReportRequest( property=f"properties/{PROPERTY_ID}", date_ranges=[DateRange(start_date="30daysAgo", end_date="yesterday")], dimensions=[Dimension(name="sessionSource"), Dimension(name="sessionMedium")], # GA4 metric is `keyEvents` (the renamed `conversions`); use # `keyEvents:<event_name>` for a single key event, e.g. `keyEvents:purchase`. metrics=[Metric(name="sessions"), Metric(name="keyEvents")], limit=page_size, offset=offset, ) resp = client.run_report(req) for row in resp.rows: yield [v.value for v in row.dimension_values] + [v.value for v in row.metric_values] # row_count is the total matching rows; stop once we've fetched them all offset += len(resp.rows) if offset >= resp.row_count or not resp.rows: break for row in fetch_all_rows(): print(row) ``` **Notes:** - **Validate metric/dimension names against the property** before hardcoding — available fields (including your registered custom dimensions and any `keyEvents:<name>` you can request) come from `client.get_metadata(name=f"properties/{PROPERTY_ID}/metadata")`. API names are case-sensitive (`keyEvents`, not `KeyEvents`). - To report a **specific** key event, either request the metric `keyEvents:purchase`, or filter `eventName` with a `dimension_filter` and use the `eventCount` metric. - Quotas are token-based per property per day/hour; batch with `run_report` `limit`/`offset` as above and cache results rather than re-querying. ## Weekly Audit Checklist - [ ] Check Realtime for expected event flow - [ ] Verify key-event counts match backend data (±5% tolerance) - [ ] Review `(not set)` and `(other)` values in reports — indicates taxonomy / custom-definition gaps - [ ] Check data freshness in Looker Studio dashboards - [ ] Confirm Google Ads conversion imports still map to the intended GA4 key events (re-check after any GA4 default-eligibility change) - [ ] Review audience sizes for remarketing — flag if dropping unexpectedly - [ ] Audit new events in DebugView (or GTM Preview) before production rollout - [ ] Confirm Consent Mode v2 signals (`ad_user_data`, `ad_personalization`) fire for EEA traffic