Claude Skill

analytics-insights

Drive Google Analytics (GA4), Google Tag Manager, Google Search Console, and BigQuery from chat — tracking plans, GA4 reports, key-event (conversion) setup, custom dimensions and metrics, GTM audits, GSC performance, and GA4 BigQuery export queries. Use when the user wants an ana

LLM Mart · 0 points · 2 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download hyperfx-ai-marketing-skills-skills_analytics-insights-abd711a.zip · 16 KB
Part of hyperfx-ai/marketing-skills — 30 skills

Install

skills CLI npx skills add https://github.com/hyperfx-ai/marketing-skills/tree/main/skills/analytics-insights
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install hyperfx-ai-marketing-skills@llmmart
Git git clone https://github.com/hyperfx-ai/marketing-skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole hyperfx-ai/marketing-skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Analytics Insights

Operator skill for the Google measurement stack — GA4, GTM, Search Console, and BigQuery — driven directly from chat. Build a tracking plan, run reports, mark conversions, audit existing setup, and query the warehouse without leaving the conversation.

Out of scope — defer to other skills

Request Send them to
Keyword research, AI-search visibility, full SEO audit seo-research (HyperSEO toolkit — broader and richer than GSC for keyword work)
Google Ads campaign performance google-ads (campaign-level) — but GA4-side conversion attribution lives here
Meta / Facebook ads metrics meta-ads
Email program metrics email-lifecycle (provider-side)

GSC and HyperSEO overlap on search-performance data. Rule of thumb: use GSC here for the user's own site's impression / click / position data. Use HyperSEO (in seo-research) for keyword research, competitor data, AI-search visibility.

Requirements

  • Hyper MCP installed and connected. https://app.hyperfx.ai/mcp
  • At least one of these connected at https://app.hyperfx.ai/apps:
    • Google Analytics — GA4 reports, custom metrics / dimensions, key-event (conversion) management.
    • Google Tag Manager — tag / trigger / variable / workspace / version management.
    • Google Search Console — search-performance data, sitemaps, URL inspection.
    • BigQuery — SQL queries against the GA4 export (or any other dataset).

If google_analytics_ga4_reports_run, google_tag_manager_tags_manage, google_search_console_performance_get, and bigquery_execute_query are all missing from the agent's tool list, stop and tell the user to enable the Hyper MCP and connect at least one of these integrations.

How to run the tools in this skill

Every tool in this skill is named by its canonical tool name. Run it with the call your surface gives you:

Surface Find a tool Run it
MCP client (Claude, Cursor, Codex, ChatGPT) search("<what you want to do>"), then describe("<name>") call("<name>", {...})
Hyper CLI hyperai search "<what you want to do>", then hyperai describe <name> hyperai call <name> --json '{...}'

If a tool is not found, its integration is not connected or not enabled for the workspace: stop and tell the user which integration to connect.

Tool surface

Group Tools
GA4 — reporting google_analytics_ga4_reports_run, google_analytics_accounts_list, google_analytics_properties_list, google_analytics_properties_get
GA4 — properties & data streams google_analytics_ga4_properties_create, google_analytics_properties_update, google_analytics_properties_delete, google_analytics_data_streams_create, google_analytics_data_streams_list, google_analytics_data_streams_get, google_analytics_data_streams_update, google_analytics_data_streams_delete, google_analytics_data_retention_get (read-only — no update variant in MCP), google_analytics_data_collection_acknowledge
GA4 — key events (conversions) google_analytics_key_events_create, google_analytics_key_events_list, google_analytics_key_events_get, google_analytics_key_events_update, google_analytics_key_events_delete
GA4 — custom metrics / dimensions google_analytics_custom_metrics_create, google_analytics_custom_metrics_list, google_analytics_custom_metrics_get, google_analytics_custom_metrics_update, google_analytics_custom_metrics_archive, google_analytics_custom_dimensions_create, google_analytics_custom_dimensions_list, google_analytics_custom_dimensions_get, google_analytics_custom_dimensions_update, google_analytics_custom_dimensions_archive
GTM google_tag_manager_accounts_manage, google_tag_manager_containers_manage, google_tag_manager_workspaces_manage, google_tag_manager_tags_manage, google_tag_manager_triggers_manage, google_tag_manager_variables_manage, google_tag_manager_built_in_variables_manage, google_tag_manager_folders_manage, google_tag_manager_environments_manage, google_tag_manager_versions_manage, google_tag_manager_version_headers_manage, google_tag_manager_user_permissions_manage, google_tag_manager_clients_manage, google_tag_manager_templates_manage, google_tag_manager_transformations_manage, google_tag_manager_zones_manage, google_tag_manager_destinations_manage
Google Search Console google_search_console_performance_get, google_search_console_sites_list, google_search_console_sitemaps_list, google_search_console_sitemaps_get, google_search_console_sitemaps_submit, google_search_console_sitemaps_delete, google_search_console_urls_submit
BigQuery bigquery_execute_query, bigquery_insert_rows

Critical rules

  1. GA4 property IDs — arg name differs by tool. Three patterns: (a) Reporting tools (run_ga4_report, get_property) take property_id="properties/123456789". (b) Create and list tools (create_key_event, list_key_events, create_custom_dimension, list_custom_dimensions, etc.) take parent="properties/123456789". (c) Get/update/delete tools operate on a specific resource and take name= with the full resource path (e.g. "properties/123456789/keyEvents/12345"). All three need the properties/ prefix in some form — passing a bare numeric ID silently fails. When in doubt, check the tool's schema for which arg is marked required.
  2. Date ranges are inclusive on both ends. start_date="2026-04-01" and end_date="2026-04-30" returns 30 days, not 29. Same for relative dates: 7daysAgo to today is 8 days, not 7.
  3. GA4 sampling kicks in above ~10M events. For high-volume properties, the GA4 API silently samples results. If precision matters (board reporting, financial attribution), use the BigQuery GA4 export instead — see references/bigquery-ga4-export.md.
  4. Conversions in GA4 are "key events". GA4 renamed "conversions" to "key events" in 2024. The tools reflect this — use google_analytics_key_events_create to mark an event as a conversion. Don't get confused by older docs.
  5. GTM changes need a workspace + version + publish. Tags / triggers / variables created in a workspace are not live until the workspace is committed to a new version and that version is published. Use google_tag_manager_workspaces_manage → modify → google_tag_manager_versions_manage (create) → publish.
  6. GSC data has a 2–3 day lag. Don't query "yesterday" in GSC and expect data — query 3+ days back for stable numbers. GA4 has a 24-48h lag for some metrics.
  7. Apple Mail Privacy Protection inflates GA4 "engaged" sessions from email. Don't trust email-driven engagement numbers in GA4 alone — cross-reference with the email provider's own click data.

Workflow — pick the right path

The skill covers four distinct jobs. Pick first; the workflows are different.

The user wants… Path Reference
A report ("how did we do last month?") Phase R —
To set up tracking ("we need to measure X") Phase T references/ga4-tracking-plan.md
To audit existing GTM / GA4 ("why is conversion data wonky?") Phase A references/gtm-audit.md
Precise / unsampled / cross-source analysis Phase B references/bigquery-ga4-export.md

Phase R — Run a report (most common path)

Warehouse option: When the workspace has an analytics warehouse connection, discover its tables and schema and query them through database_query. Read Warehouse Data Status for the selected connection and report its last successful refresh; do not assume an hourly cache or unsampled data. Otherwise use google_analytics_ga4_reports_run for GA4 and google_search_console_performance_get for GSC. Keep account IDs, dates and metric definitions explicit.

  1. Confirm the property. google_analytics_accounts_list() → google_analytics_properties_list(filter="parent:accounts/<account_id>"). Ask the user to pick if there are multiple. Save the properties/<id> for the rest of the conversation.
  2. Pick the date range. Always confirm. "Last 30 days" is start_date="30daysAgo", end_date="yesterday" (avoid today — partial-day data is unstable).
  3. Pick metrics + dimensions. Don't blast 12 metrics × 6 dimensions in one report — the result is unreadable. Pick the 2–3 metrics that answer the user's question and the 1–2 dimensions that segment them meaningfully.
  4. Run the report.
google_analytics_ga4_reports_run(
  property_id="properties/123456789",
  start_date="30daysAgo",
  end_date="yesterday",
  metrics=["activeUsers", "sessions", "conversions", "totalRevenue"],
  dimensions=["sessionDefaultChannelGroup", "deviceCategory"],
)
  1. Present results as a table. Always show the raw numbers alongside any interpretation. "Organic search drove 12,400 sessions (+18% MoM)" beats "organic was up."
  2. One follow-on if the data flags it. If a metric stands out (e.g., conversion rate dropped 40% on mobile), run one targeted follow-up report — don't speculate.

Common GA4 metrics & dimensions

The GA4 API uses camelCase names. The most useful:

Metrics: activeUsers, sessions, screenPageViews, bounceRate, engagementRate, averageSessionDuration, conversions, eventCount, totalRevenue, transactions, purchaseRevenue, userEngagementDuration.

Dimensions: country, city, deviceCategory, operatingSystem, browser, sessionDefaultChannelGroup, sessionSource, sessionMedium, sessionCampaignName, pagePath, eventName, date, hour, landingPage.

For the full list, the user can browse the GA4 Data API reference.

Phase T — Set up tracking

The GA4 API can create properties, data streams, key events, custom metrics, and custom dimensions — but it can't deploy GTM tags into the page. That step is GTM-side (or hard-coded in the site). The skill workflow:

  1. Tracking plan first. Define what events you need to fire, where they fire, and which ones are conversions (key events). Full template in references/ga4-tracking-plan.md.
  2. GA4-side setup — create custom dimensions / metrics, mark key events:
google_analytics_custom_dimensions_create(
  parent="properties/123456789",
  parameter_name="plan_tier",
  display_name="Plan Tier",
  scope="EVENT",
)

google_analytics_key_events_create(
  parent="properties/123456789",
  event_name="purchase",
  counting_method="ONCE_PER_EVENT",
)
  1. GTM-side setup — create/update tags + triggers + variables in a workspace, then version + publish:
google_tag_manager_workspaces_manage(operation="create", account_id="...", container_id="...", name="purchase-tracking-v3")

google_tag_manager_tags_manage(operation="create", workspace_path="...", tag_definition={...})
google_tag_manager_triggers_manage(operation="create", workspace_path="...", trigger_definition={...})
google_tag_manager_variables_manage(operation="create", workspace_path="...", variable_definition={...})

google_tag_manager_versions_manage(operation="create", workspace_path="...", version_name="purchase-tracking-v3")
# then publish via the GTM UI or version operation
  1. Validate — see Phase A.

Phase A — Audit (the existing setup is broken or suspect)

Most "our analytics is wrong" complaints are one of:

Symptom Likely cause How to confirm
Conversion event not appearing in GA4 Event firing in GTM but not reaching GA4 (wrong measurement ID, blocked by consent gate, ad blocker) google_analytics_ga4_reports_run for eventName=purchase over the last 7d → if 0, check GTM
Conversion count wildly off Event firing on every page (not just confirmation), or duplicate tags Audit GTM tags via google_tag_manager_tags_manage(operation="list"), check for multiple tags firing on the same trigger
Revenue reported differently in GA4 vs the platform of record Currency mismatch, refund handling, attribution window Pull both side-by-side, look for refund / currency rows
Suddenly mobile traffic dropped to ~0 Tag firing only on desktop trigger, or a recent GTM publish broke the mobile container google_tag_manager_versions_manage(operation="list") to find recent publishes, diff with previous version
GSC clicks ≠ GA4 organic sessions Always different — different definitions. Don't try to reconcile exactly. Expected; document and move on

Detailed audit walkthrough in references/gtm-audit.md.

Phase B — BigQuery GA4 export (precision / cross-source analysis)

For unsampled data, custom attribution, joining GA4 with order-DB / CRM data, or cohort analysis. Requires the GA4 → BigQuery export to be turned on in the GA4 admin (free for standard properties since 2023).

bigquery_execute_query(
  query="""
    SELECT
      event_date,
      COUNT(DISTINCT user_pseudo_id) AS users,
      COUNTIF(event_name = 'purchase') AS purchases,
      SUM(IF(event_name = 'purchase', ecommerce.purchase_revenue, 0)) AS revenue
    FROM `your-project.analytics_123456789.events_*`
    WHERE _TABLE_SUFFIX BETWEEN '20260401' AND '20260430'
    GROUP BY event_date
    ORDER BY event_date
  """
)

Schema, common queries, and the full attribution-modeling workflow in references/bigquery-ga4-export.md.

Output standards

  • Always present numbers as tables, not prose. "12,400 sessions, 8.2% bounce, 1.4% conversion" → markdown table.
  • Always include the date range in the report header. "Apr 1–30, 2026" — undated numbers are useless.
  • Always include the property / site the report came from. Multi-property organizations get burned by this constantly.
  • Annotate sampling. If GA4 returns a samplesReadCount < samplingSpaceSize, say so explicitly. The user needs to know whether to trust the number for finance / board reporting.
  • Distinguish "ratio metric" from "summable metric". Bounce rate, engagement rate, conversion rate are ratios and don't sum — present them as one number, not a column total.

Reference workflows

Reference When to read
references/ga4-tracking-plan.md Designing what to track — recommended event schema, custom dimensions / metrics, key-event mapping, naming conventions
references/gtm-audit.md Auditing an existing GTM container — finding duplicate tags, broken triggers, unused variables, missing consent gates
references/bigquery-ga4-export.md Querying the GA4 BigQuery export — schema, common queries (DAU, funnel, attribution, cohort, retention), join patterns
Files (marketing-skills)
  • references
    • bigquery-ga4-export.md 9.1 KB
      # BigQuery GA4 Export
      
      The GA4 → BigQuery export is the single most under-used analytics asset most teams have. It gives you:
      
      - **Unsampled data** — no GA4 API sampling, no matter how big the property.
      - **Event-level granularity** — every event row, queryable.
      - **Joinability** — join GA4 data with your order DB, CRM, billing data.
      - **Custom attribution** — any model you can write in SQL, not just the four GA4 ships.
      - **Cohort and retention queries** — clean window functions instead of GA4's locked-in cohort report.
      
      Free for standard GA4 properties since 2023. Configure in GA4 Admin → BigQuery Linking. After it's set up, every day's events arrive as a `events_YYYYMMDD` table in `<project>.analytics_<property_id>.events_*`.
      
      ## Schema cheat sheet
      
      The export has one row per event. Key columns:
      
      | Column | Type | What |
      | --- | --- | --- |
      | `event_date` | STRING | `YYYYMMDD` (use `_TABLE_SUFFIX` for partition pruning) |
      | `event_timestamp` | INT64 | Microseconds since epoch (UTC) |
      | `event_name` | STRING | The event name (`purchase`, `page_view`, etc.) |
      | `event_params` | RECORD (REPEATED) | Array of `{key, value: {string_value, int_value, double_value, float_value}}` |
      | `user_id` | STRING | Set if you set it via `gtag('config', '...', {user_id})` |
      | `user_pseudo_id` | STRING | GA4-assigned pseudo-anonymous client ID |
      | `user_properties` | RECORD (REPEATED) | Array of `{key, value: ...}` |
      | `device.*` | RECORD | `category`, `mobile_brand_name`, `operating_system`, `browser` |
      | `geo.*` | RECORD | `country`, `region`, `city`, `continent` |
      | `traffic_source.*` | RECORD | `name`, `medium`, `source` |
      | `session_traffic_source_last_click.*` | RECORD | The session's last-click attribution |
      | `ecommerce.*` | RECORD | `purchase_revenue`, `transaction_id`, `total_item_quantity` |
      | `items` | RECORD (REPEATED) | Cart contents — `item_id`, `item_name`, `price`, `quantity` |
      
      Two patterns dominate:
      
      **Extract a parameter:** unnest `event_params` and pick by key.
      
      ```sql
      (SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'page_path') AS page_path
      ```
      
      **Build session ID:** GA4 doesn't ship a session ID column directly — derive it from `user_pseudo_id` + `ga_session_id` event param.
      
      ```sql
      CONCAT(
        user_pseudo_id,
        '.',
        CAST((SELECT value.int_value FROM UNNEST(event_params) WHERE key = 'ga_session_id') AS STRING)
      ) AS session_id
      ```
      
      ## Query templates (copy + adapt)
      
      Replace `your-project.analytics_123456789` with your actual project and property ID throughout.
      
      ### 1. Daily active users + sessions
      
      ```sql
      SELECT
        PARSE_DATE('%Y%m%d', event_date) AS date,
        COUNT(DISTINCT user_pseudo_id) AS daily_active_users,
        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 '20260401' AND '20260430'
      GROUP BY date
      ORDER BY date;
      ```
      
      ### 2. Top pages by views
      
      ```sql
      SELECT
        (SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'page_path') AS page_path,
        COUNT(*) AS page_views,
        COUNT(DISTINCT user_pseudo_id) AS unique_users
      FROM `your-project.analytics_123456789.events_*`
      WHERE _TABLE_SUFFIX BETWEEN '20260401' AND '20260430'
        AND event_name = 'page_view'
      GROUP BY page_path
      ORDER BY page_views DESC
      LIMIT 50;
      ```
      
      ### 3. Conversion funnel (begin_checkout → purchase)
      
      ```sql
      WITH steps AS (
        SELECT
          user_pseudo_id,
          MAX(IF(event_name = 'view_item', 1, 0)) AS step_1_view,
          MAX(IF(event_name = 'add_to_cart', 1, 0)) AS step_2_add,
          MAX(IF(event_name = 'begin_checkout', 1, 0)) AS step_3_checkout,
          MAX(IF(event_name = 'purchase', 1, 0)) AS step_4_purchase
        FROM `your-project.analytics_123456789.events_*`
        WHERE _TABLE_SUFFIX BETWEEN '20260401' AND '20260430'
        GROUP BY user_pseudo_id
      )
      SELECT
        SUM(step_1_view)     AS viewed_item,
        SUM(step_2_add)      AS added_to_cart,
        SUM(step_3_checkout) AS began_checkout,
        SUM(step_4_purchase) AS purchased,
        ROUND(SUM(step_4_purchase) / NULLIF(SUM(step_1_view), 0) * 100, 2) AS overall_conversion_pct
      FROM steps;
      ```
      
      ### 4. Revenue by source/medium (last-click attribution)
      
      ```sql
      SELECT
        session_traffic_source_last_click.manual_campaign.source AS source,
        session_traffic_source_last_click.manual_campaign.medium AS medium,
        COUNT(DISTINCT ecommerce.transaction_id) AS purchases,
        SUM(ecommerce.purchase_revenue) AS revenue
      FROM `your-project.analytics_123456789.events_*`
      WHERE _TABLE_SUFFIX BETWEEN '20260401' AND '20260430'
        AND event_name = 'purchase'
      GROUP BY source, medium
      ORDER BY revenue DESC;
      ```
      
      ### 5. New vs returning users
      
      ```sql
      WITH first_seen AS (
        SELECT
          user_pseudo_id,
          MIN(PARSE_DATE('%Y%m%d', event_date)) AS first_date
        FROM `your-project.analytics_123456789.events_*`
        WHERE _TABLE_SUFFIX BETWEEN '20260101' AND '20260430'
        GROUP BY user_pseudo_id
      )
      SELECT
        PARSE_DATE('%Y%m%d', e.event_date) AS date,
        COUNT(DISTINCT IF(fs.first_date = PARSE_DATE('%Y%m%d', e.event_date), e.user_pseudo_id, NULL)) AS new_users,
        COUNT(DISTINCT IF(fs.first_date < PARSE_DATE('%Y%m%d', e.event_date), e.user_pseudo_id, NULL)) AS returning_users
      FROM `your-project.analytics_123456789.events_*` e
      JOIN first_seen fs USING (user_pseudo_id)
      WHERE _TABLE_SUFFIX BETWEEN '20260401' AND '20260430'
      GROUP BY date
      ORDER BY date;
      ```
      
      ### 6. Weekly retention cohort (week-over-week)
      
      ```sql
      WITH first_visit AS (
        SELECT
          user_pseudo_id,
          DATE_TRUNC(PARSE_DATE('%Y%m%d', MIN(event_date)), WEEK) AS cohort_week
        FROM `your-project.analytics_123456789.events_*`
        WHERE _TABLE_SUFFIX BETWEEN '20260101' AND '20260430'
        GROUP BY user_pseudo_id
      ),
      visits AS (
        SELECT DISTINCT
          user_pseudo_id,
          DATE_TRUNC(PARSE_DATE('%Y%m%d', event_date), WEEK) AS visit_week
        FROM `your-project.analytics_123456789.events_*`
        WHERE _TABLE_SUFFIX BETWEEN '20260101' AND '20260430'
      )
      SELECT
        fv.cohort_week,
        DATE_DIFF(v.visit_week, fv.cohort_week, WEEK) AS weeks_since_first_visit,
        COUNT(DISTINCT v.user_pseudo_id) AS users
      FROM first_visit fv
      JOIN visits v USING (user_pseudo_id)
      GROUP BY cohort_week, weeks_since_first_visit
      ORDER BY cohort_week, weeks_since_first_visit;
      ```
      
      ### 7. Joining with your order database
      
      The most powerful query in the bunch. Assumes the site sets `user_id` on `gtag('config', ..., {user_id})` and that ID matches your DB.
      
      ```sql
      WITH ga AS (
        SELECT
          user_id,
          DATE(TIMESTAMP_MICROS(event_timestamp)) AS visit_date,
          (SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'page_path') AS page_path
        FROM `your-project.analytics_123456789.events_*`
        WHERE _TABLE_SUFFIX BETWEEN '20260401' AND '20260430'
          AND user_id IS NOT NULL
      )
      SELECT
        ga.user_id,
        ga.page_path,
        COUNT(*) AS page_visits,
        ANY_VALUE(orders.lifetime_revenue_cents) AS lifetime_revenue_cents,
        ANY_VALUE(orders.plan_tier) AS plan_tier
      FROM ga
      LEFT JOIN `your-project.your_dataset.orders` orders USING (user_id)
      GROUP BY user_id, page_path
      ORDER BY page_visits DESC
      LIMIT 100;
      ```
      
      This unlocks questions GA4 can never answer alone: *"What's the LTV of users who hit our pricing page in the last 30 days?"*
      
      ## Running queries through the MCP
      
      ```
      bigquery_execute_query(
        query="""
          SELECT event_date, COUNT(DISTINCT user_pseudo_id) AS dau
          FROM `your-project.analytics_123456789.events_*`
          WHERE _TABLE_SUFFIX BETWEEN '20260401' AND '20260430'
          GROUP BY event_date
          ORDER BY event_date
        """,
        # Optional: project_id, location, etc. — depends on Pipedream proxy config
      )
      ```
      
      For inserting rows back into BigQuery (e.g., writing a derived metrics table):
      
      ```
      bigquery_insert_rows(
        table_id="your-project.your_dataset.daily_metrics",
        rows=[
          {"date": "2026-04-30", "metric_name": "dau", "value": 12400},
          ...
        ],
      )
      ```
      
      ## Performance + cost rules
      
      - **Always partition-prune.** Use `_TABLE_SUFFIX BETWEEN 'YYYYMMDD' AND 'YYYYMMDD'` to limit which daily tables get scanned. A query without this against a year of GA4 events is *expensive*.
      - **Don't `SELECT *`.** Pick the columns you need. GA4 events have ~30 nested fields; selecting all of them on a year of data is a wallet event.
      - **Cache exploratory queries.** BigQuery caches identical queries for 24h at no cost. Iterating on a query? Don't rewrite the date range every time — cache hit gets you free re-runs.
      - **Use the `intraday_*` tables for today's data.** GA4 export populates `intraday_YYYYMMDD` for the current day, then collapses into `events_YYYYMMDD` ~24h later. If you need today's data, query both with `events_*` glob.
      
      ## Schema gotchas
      
      - **`event_date` is a STRING (`YYYYMMDD`).** Convert with `PARSE_DATE('%Y%m%d', event_date)` before doing date arithmetic.
      - **`event_timestamp` is microseconds.** `TIMESTAMP_MICROS(event_timestamp)` to get a real timestamp.
      - **Nested params require `UNNEST`.** This is the #1 thing new GA4 BigQuery users get stuck on. Use the patterns above as templates.
      - **Ecommerce data lives in `ecommerce.*` and `items` (repeated).** `purchase` events have populated `ecommerce.purchase_revenue`; `add_to_cart` events have populated `items` but no `ecommerce` revenue.
      - **`user_id` is NULL for anonymous users.** Don't filter on it unless you specifically want logged-in users.
      
    • ga4-tracking-plan.md 9 KB
      # GA4 Tracking Plan
      
      A tracking plan is the contract between the product (what we instrument) and the analytics surface (what we report on). Build it *before* writing any GTM tags. Skipping this step is the #1 reason GA4 setups end up unusable six months in.
      
      ## What a tracking plan contains
      
      For each event you intend to fire:
      
      1. **Event name** — `snake_case`, verb-led (e.g., `purchase`, `start_checkout`, `play_video`).
      2. **Trigger** — when the event fires. Be precise: "click on `[data-ev=cta-pricing]`" beats "user clicks a button."
      3. **Parameters** — the data you ship with the event. Each parameter has a name, type, source, example value.
      4. **Is it a key event (conversion)?** — yes / no.
      5. **Where it's set up** — GTM tag name, or hardcoded in the codebase.
      6. **Owner** — who's responsible if it breaks.
      
      Anything missing from this list will burn the team later.
      
      ## Recommended event taxonomy
      
      GA4 has [recommended events](https://support.google.com/analytics/answer/9267735) that get special treatment in the UI (auto-populated reports, predictive audiences). Use them where they fit; only invent custom events for things they don't cover.
      
      ### For all sites
      
      | Event | When | Key event? |
      | --- | --- | --- |
      | `page_view` | Every page load (auto with GA4 base config) | No |
      | `scroll` | 90% scroll depth (auto with enhanced measurement) | No |
      | `click` | Outbound link click (auto with enhanced measurement) | No |
      | `view_search_results` | Site search (auto with enhanced measurement) | No |
      | `file_download` | PDF / docx / zip download (auto with enhanced measurement) | No |
      | `form_submit` *(custom or auto)* | Form submitted | Often (lead form) |
      | `sign_up` | Account creation completed | Yes |
      | `login` | Returning user logs in | No |
      
      ### For ecommerce (use exactly these names — they unlock the GA4 Monetization reports)
      
      | Event | When | Key event? |
      | --- | --- | --- |
      | `view_item_list` | Product list / collection page viewed | No |
      | `view_item` | Product detail page viewed | No |
      | `select_item` | Product clicked from a list | No |
      | `add_to_cart` | Item added to cart | No |
      | `view_cart` | Cart viewed | No |
      | `begin_checkout` | Checkout started | Often |
      | `add_payment_info` | Payment info added | No |
      | `add_shipping_info` | Shipping info added | No |
      | `purchase` | Order completed | **Yes** |
      | `refund` | Order refunded (full or partial) | No |
      
      For the ecom events, GA4 expects specific parameter names — `currency`, `value`, `items[]` with `item_id`, `item_name`, `price`, `quantity`. Off-spec parameters silently fall out of the Monetization reports.
      
      ### For SaaS / product-led
      
      | Event | When | Key event? |
      | --- | --- | --- |
      | `sign_up` | Account created | Yes |
      | `start_trial` | Free trial started | Yes |
      | `complete_onboarding` | First-run setup finished | Yes (activation) |
      | `feature_use_<name>` | Specific feature used (rate-limit to once per session) | Sometimes |
      | `upgrade_plan` | Plan upgraded | **Yes** |
      | `downgrade_plan` | Plan downgraded | No (but track) |
      | `cancel_subscription` | Subscription canceled | No (but track) |
      
      ## Custom dimensions vs custom metrics vs event params
      
      GA4 distinguishes:
      
      - **Event parameters** — data attached to a single event. Available in the API as `event_params.<name>` in the BigQuery export.
      - **User properties** — attached to a user, persist across sessions. Use for plan tier, signup cohort, account type.
      - **Custom dimensions** — *register* an event parameter or user property as a dimension you can group by in GA4 reports. **Until you register it, you can't use it as a dimension in the GA4 UI or `run_ga4_report`.**
      - **Custom metrics** — *register* a numeric event parameter as a metric you can sum / average. Same deal: unregistered = unreportable.
      
      **Rule:** every parameter you want to *report on* must be registered as either a custom dimension (string) or custom metric (number). Register them ahead of the event firing — backfill doesn't happen for older data.
      
      ```
      google_analytics_custom_dimensions_create(
        parent="properties/123456789",       # note: arg is "parent", not "property_id"
        parameter_name="plan_tier",          # the event param name
        display_name="Plan Tier",            # what shows in the UI
        scope="EVENT",                       # or "USER" for user properties
        description="Plan the user is on at time of event",
      )
      
      google_analytics_custom_metrics_create(
        parent="properties/123456789",       # note: arg is "parent", not "property_id"
        parameter_name="lesson_seconds",
        display_name="Lesson Duration (s)",
        measurement_unit="SECONDS",
        scope="EVENT",
      )
      ```
      
      GA4 limits: 50 custom event-scoped dimensions, 25 user-scoped, 50 custom metrics per property (standard tier). Use them sparingly — registered dimensions are forever (you can archive but not delete).
      
      ## Naming conventions
      
      A consistent naming scheme is the single highest-leverage thing in a tracking plan. Pick one and enforce.
      
      - **Events: `snake_case`, verb-led.** `add_to_cart`, not `addedToCart` or `Add to Cart`. Aligns with GA4 recommended events.
      - **Parameters: `snake_case`, noun.** `item_id`, `plan_tier`, `referrer_path`. Avoid abbreviations.
      - **Custom dimensions: human-readable display names.** "Plan Tier", "Signup Cohort". The display name is what shows in reports — `plan_tier` is fine for the param, "Plan Tier" is what the user sees.
      - **Boolean params: prefix with `is_` or `has_`.** `is_returning_user`, `has_active_subscription`.
      - **Don't use reserved names.** GA4 reserves `ga_session_id`, `_session_id`, `firebase_*`, etc. The list is in the GA4 docs.
      
      ## Mapping the plan to key events
      
      Not every event is a conversion. Mark only the ones that map to *business outcomes*:
      
      | Site type | Typical key events |
      | --- | --- |
      | Ecommerce | `purchase`, sometimes `begin_checkout` (for funnel midpoint reporting) |
      | SaaS | `sign_up`, `start_trial`, `complete_onboarding`, `upgrade_plan` |
      | Lead-gen / B2B | `form_submit` (with `form_id=demo-request`), `book_meeting` |
      | Content / publisher | `subscribe_newsletter`, time-on-site engagement (custom) |
      
      Mark with `google_analytics_key_events_create`:
      
      ```
      google_analytics_key_events_create(
        parent="properties/123456789",     # note: arg is "parent", not "property_id"
        event_name="purchase",
        counting_method="ONCE_PER_EVENT",  # or ONCE_PER_SESSION
      )
      ```
      
      `counting_method` matters:
      - `ONCE_PER_EVENT` — every fire counts. Use for purchases, form submits.
      - `ONCE_PER_SESSION` — first fire in a session counts. Use for low-friction events like "viewed pricing page" you might mark as a soft conversion.
      
      ## Example tracking plan (excerpt — SaaS)
      
      ```
      | Event              | Trigger                           | Params                                          | Key event? | Setup        | Owner |
      |--------------------|-----------------------------------|-------------------------------------------------|-----------|--------------|-------|
      | page_view          | Every page                        | page_path, page_title                           | No        | GA4 auto     | growth |
      | sign_up            | After /api/users POST 201         | method (email|google), plan_tier_intent         | Yes       | GTM tag #14  | growth |
      | start_trial        | After /api/billing/trials POST    | plan, trial_days                                | Yes       | GTM tag #15  | growth |
      | complete_onboarding| After last onboarding step        | onboarding_seconds                              | Yes       | GTM tag #16  | product |
      | upgrade_plan       | On Stripe webhook checkout.success| from_plan, to_plan, mrr_delta_cents             | Yes       | server-side  | finance |
      | feature_use_export | On click of [data-ev=export-csv]  | report_type, row_count                          | No        | GTM tag #22  | product |
      ```
      
      The "Setup" column tells the auditor *where to look* when something breaks. The "Owner" column tells you *who to ping*.
      
      ## Anti-patterns
      
      - **One mega-event with 30 parameters.** Splits the data badly and hits GA4 param limits. Split into multiple events with focused param sets.
      - **Same event name fired in multiple meanings.** `click` with no qualifying parameter is useless. Either name the events differently (`click_cta_hero`, `click_cta_pricing`) or attach a `cta_id` parameter.
      - **Sending PII as parameters.** GA4 explicitly prohibits emails, phone numbers, full names. Hash or remove before sending.
      - **Forgetting to register the dimension.** Param fires, never appears in the UI. Hours of confusion.
      - **Renaming events after launch.** GA4 treats renamed events as new — old data won't merge. Pick names carefully the first time.
      
      ## What to do with this plan
      
      1. **Review with engineering and product.** They have to fire the events; sign-off is non-negotiable.
      2. **Implement in GTM first** for marketing-page events, **server-side** for billing / auth events.
      3. **Register the custom dimensions and metrics in GA4** before any event ships.
      4. **QA in GA4 DebugView** for the first week — every new event should show up there in real time.
      5. **Document the plan somewhere durable** (Notion, Confluence, repo README) — the plan is the source of truth, not the GTM container.
      
    • gtm-audit.md 7.9 KB
      # GTM Audit
      
      A standard auditing pass against an existing Google Tag Manager container. Run this when:
      
      - Conversion data looks wrong in GA4.
      - A recent GTM publish broke something and nobody is sure what.
      - A new team has inherited a container someone else built.
      - The container has > 30 tags and nobody can remember what they all do.
      
      The Hyper MCP exposes the GTM API as `gtm_*` tools (note: the prefix is `gtm_*`, not `google_tag_manager_*`). Each tool is one entity with `operation` parameters (`get`, `list`, `create`, `update`, `delete`, etc.).
      
      ## Audit checklist
      
      The 8 checks below cover ~95% of GTM problems.
      
      ### 1. Inventory: how many tags, triggers, variables?
      
      ```
      google_tag_manager_tags_manage(operation="list", workspace_path="accounts/123/containers/456/workspaces/789")
      google_tag_manager_triggers_manage(operation="list", workspace_path="...")
      google_tag_manager_variables_manage(operation="list", workspace_path="...")
      ```
      
      Healthy ranges (rough — bigger sites legitimately have more):
      
      | Entity | < 30 tags | 30–80 | > 80 |
      | --- | --- | --- | --- |
      | Tags | Easy to understand | Audit needed every quarter | Likely has duplicates / stale tags |
      | Triggers | Probably fine | Look for unused | Most are unused — most teams over-create triggers |
      | Variables | Fine | Some likely unused | Definitely has unused ones |
      
      If totals are at the high end of any row, schedule a cleanup pass.
      
      ### 2. Find duplicate tags
      
      The most common GTM problem: two tags fire on the same trigger because someone created a "v2" without disabling the original. Result: events fire twice → conversion counts double.
      
      Pull all tags, group by `type` + the trigger they fire on. Anything firing the same event to the same destination on the same trigger is suspect.
      
      ```
      # Get all tags
      result = google_tag_manager_tags_manage(operation="list", workspace_path="...")
      
      # Look for duplicates: same type, same firing trigger, similar parameters
      # (No SQL here — just inspect the response)
      ```
      
      Confirm against GA4 DebugView: trigger the page, see if the event fires once or twice.
      
      ### 3. Find tags with no firing triggers
      
      A tag with `firingTriggerId: []` is dead — it can never fire. Either delete it or attach the missing trigger.
      
      Common cause: someone deleted the trigger but not the tag. The tag sits there confusing future readers.
      
      ### 4. Find triggers with no tags
      
      A trigger nothing depends on is just clutter. Delete.
      
      ```
      # After listing tags + triggers, build a set of trigger IDs in use:
      used = set()
      for tag in tags:
          used.update(tag.firing_trigger_id or [])
          used.update(tag.blocking_trigger_id or [])
      
      unused_triggers = [t for t in triggers if t.trigger_id not in used]
      ```
      
      ### 5. Find variables that aren't referenced
      
      GTM variables are referenced from tags and triggers. If a variable name appears nowhere in any tag's parameters or trigger's filters, delete it.
      
      ```
      # Search the JSON of every tag + trigger for {{Variable Name}} references
      ```
      
      ### 6. Check for missing consent gates (GDPR / CCPA)
      
      If the site collects EU traffic, every analytics + advertising tag should be gated on a consent variable. The pattern:
      
      - A consent variable (`Cookiebot Consent State`, `OneTrust Active Groups`, custom JS variable) returns the user's consent status.
      - Each marketing / analytics tag has a *blocking trigger* on `Consent != Granted`.
      
      Without this, GDPR fines are real.
      
      ```
      # For every analytics / advertising tag, verify it has a blocking trigger
      # on the consent variable. Tags missing the gate are flagged.
      ```
      
      GA4 has built-in consent mode that can do this differently — check whether the container is using consent mode v2 or manual gates. Either is fine, missing both is not.
      
      ### 7. Check for hard-coded measurement IDs / property IDs
      
      Bad: GA4 measurement ID `G-XXXXXXX` typed directly into 14 different tags.
      Good: GA4 measurement ID stored in a single Constant variable, referenced from all tags.
      
      Cleanup: create a `Constant` variable, replace every hard-coded reference. Critical when migrating between properties.
      
      ### 8. Check version history for recent changes
      
      If something broke "around last Tuesday," the publish history is the prime suspect.
      
      ```
      google_tag_manager_versions_manage(operation="list", container_path="accounts/123/containers/456")
      google_tag_manager_version_headers_manage(operation="list", container_path="accounts/123/containers/456")
      ```
      
      Each version has a `publishedTimestamp` and a name. Find the version that was live during the breakage window. Diff against the previous version (the GTM UI does this best — the API can fetch both versions and you compare the JSON).
      
      ```
      google_tag_manager_versions_manage(operation="get", version_path="accounts/.../versions/12")
      google_tag_manager_versions_manage(operation="get", version_path="accounts/.../versions/11")
      ```
      
      If a tag / trigger / variable changed between v11 and v12 and v12 was the publish that broke things, you have your culprit.
      
      ## The publish workflow (don't break this part)
      
      GTM has a 4-stage workflow that exists for a reason — bypass it and you'll publish broken tags to production.
      
      ```
      1. Workspace      ← edit here (sandboxed, doesn't affect live container)
         google_tag_manager_workspaces_manage(operation="create", name="audit-2026-q2", ...)
      
      2. Preview         ← test in GTM Preview mode with the page open
         (UI-only — no API operation, but the agent can tell the user to do this)
      
      3. Version         ← snapshot the workspace into a versioned set of changes
         google_tag_manager_versions_manage(operation="create", workspace_path="...", name="audit-cleanup", notes="removed 12 unused tags")
      
      4. Publish         ← make the version live
         (UI or version operation with publish=true)
      ```
      
      **Never edit directly in the default workspace** if more than one person uses the container. Create a named workspace, do the work, version + publish, delete the workspace.
      
      ## Common audit findings (what they usually mean)
      
      | Finding | Likely cause | Fix |
      | --- | --- | --- |
      | 4× the expected `purchase` events in GA4 | Two tags fire on the same trigger | Disable / delete the duplicate |
      | Conversion event missing entirely | Tag exists but blocking trigger fires (consent, page exclusion) | Check blocking triggers, verify consent state |
      | Events firing on the wrong domain | Trigger lacks a `Page Hostname` filter | Add hostname filter to trigger |
      | Form submission tracked, but no form data captured | Form variables not registered as DataLayer Variables | Create DataLayer Variables for each field |
      | Tag fires on every page instead of just confirmation | Trigger is `All Pages` instead of a URL-specific trigger | Tighten the trigger condition |
      | Old tag firing a Universal Analytics event | Container never migrated when GA4 launched | Delete UA tags (UA was sunset July 2024) |
      | New page type missing all tracking | Page template doesn't have the GTM container snippet | Verify the GTM script tag is on every page template |
      
      ## Reporting the audit
      
      Present findings as a table the team can act on:
      
      ```
      | # | Finding                                  | Severity | Action                       | Tag / Trigger ID |
      |---|------------------------------------------|----------|------------------------------|------------------|
      | 1 | Duplicate `purchase` event tag           | High     | Delete tag #47               | tag_47           |
      | 2 | 14 unused triggers                       | Low      | Delete                       | (list)           |
      | 3 | GA4 measurement ID hardcoded in 8 tags   | Medium   | Move to Constant variable    | (list)           |
      | 4 | EU traffic, no consent gate on Meta tag  | High     | Add consent blocking trigger | tag_22           |
      ```
      
      Severity rubric:
      - **High** = data is currently wrong / privacy non-compliant. Fix this week.
      - **Medium** = will become a problem (migration risk, maintenance burden). Fix this month.
      - **Low** = cleanup, no functional impact. Fix when convenient.
      
      After the audit, the team should know exactly what to delete, what to fix, and in what order.
      
  • SKILL.md 15.5 KB
    ---
    name: analytics-insights
    description: Drive Google Analytics (GA4), Google Tag Manager, Google Search Console, and BigQuery from chat — tracking plans, GA4 reports, key-event (conversion) setup, custom dimensions and metrics, GTM audits, GSC performance, and GA4 BigQuery export queries. Use when the user wants an analytics audit, a GA4 report, a tracking plan, conversion setup, GTM cleanup, search-performance data, or asks "how is the site performing?" or "are my conversions firing?".
    requires_toolkits:
      - google_analytics_toolkit
    icon: google_analytics
    short_description: Drive GA4, GTM, Search Console, and BigQuery from chat for reports and tracking.
    ---
    
    # Analytics Insights
    
    Operator skill for the Google measurement stack — GA4, GTM, Search Console, and BigQuery — driven directly from chat. Build a tracking plan, run reports, mark conversions, audit existing setup, and query the warehouse without leaving the conversation.
    
    ## Out of scope — defer to other skills
    
    | Request | Send them to |
    | --- | --- |
    | Keyword research, AI-search visibility, full SEO audit | `seo-research` (HyperSEO toolkit — broader and richer than GSC for keyword work) |
    | Google Ads campaign performance | `google-ads` (campaign-level) — but GA4-side conversion attribution lives here |
    | Meta / Facebook ads metrics | `meta-ads` |
    | Email program metrics | `email-lifecycle` (provider-side) |
    
    GSC and HyperSEO overlap on search-performance data. Rule of thumb: use GSC here for *the user's own site's* impression / click / position data. Use HyperSEO (in `seo-research`) for keyword research, competitor data, AI-search visibility.
    
    ## Requirements
    
    - **Hyper MCP installed and connected.** [https://app.hyperfx.ai/mcp](https://app.hyperfx.ai/mcp)
    - **At least one of these connected** at [https://app.hyperfx.ai/apps](https://app.hyperfx.ai/apps):
      - **Google Analytics** — GA4 reports, custom metrics / dimensions, key-event (conversion) management.
      - **Google Tag Manager** — tag / trigger / variable / workspace / version management.
      - **Google Search Console** — search-performance data, sitemaps, URL inspection.
      - **BigQuery** — SQL queries against the GA4 export (or any other dataset).
    
    If `google_analytics_ga4_reports_run`, `google_tag_manager_tags_manage`, `google_search_console_performance_get`, and `bigquery_execute_query` are all missing from the agent's tool list, stop and tell the user to enable the Hyper MCP and connect at least one of these integrations.
    
    ### How to run the tools in this skill
    
    Every tool in this skill is named by its canonical tool name. Run it with the call your surface gives you:
    
    | Surface | Find a tool | Run it |
    | --- | --- | --- |
    | MCP client (Claude, Cursor, Codex, ChatGPT) | `search("<what you want to do>")`, then `describe("<name>")` | `call("<name>", {...})` |
    | Hyper CLI | `hyperai search "<what you want to do>"`, then `hyperai describe <name>` | `hyperai call <name> --json '{...}'` |
    
    If a tool is not found, its integration is not connected or not enabled for the workspace: stop and tell the user which integration to connect.
    
    ## Tool surface
    
    | Group | Tools |
    | --- | --- |
    | GA4 — reporting | `google_analytics_ga4_reports_run`, `google_analytics_accounts_list`, `google_analytics_properties_list`, `google_analytics_properties_get` |
    | GA4 — properties & data streams | `google_analytics_ga4_properties_create`, `google_analytics_properties_update`, `google_analytics_properties_delete`, `google_analytics_data_streams_create`, `google_analytics_data_streams_list`, `google_analytics_data_streams_get`, `google_analytics_data_streams_update`, `google_analytics_data_streams_delete`, `google_analytics_data_retention_get` *(read-only — no update variant in MCP)*, `google_analytics_data_collection_acknowledge` |
    | GA4 — key events (conversions) | `google_analytics_key_events_create`, `google_analytics_key_events_list`, `google_analytics_key_events_get`, `google_analytics_key_events_update`, `google_analytics_key_events_delete` |
    | GA4 — custom metrics / dimensions | `google_analytics_custom_metrics_create`, `google_analytics_custom_metrics_list`, `google_analytics_custom_metrics_get`, `google_analytics_custom_metrics_update`, `google_analytics_custom_metrics_archive`, `google_analytics_custom_dimensions_create`, `google_analytics_custom_dimensions_list`, `google_analytics_custom_dimensions_get`, `google_analytics_custom_dimensions_update`, `google_analytics_custom_dimensions_archive` |
    | GTM | `google_tag_manager_accounts_manage`, `google_tag_manager_containers_manage`, `google_tag_manager_workspaces_manage`, `google_tag_manager_tags_manage`, `google_tag_manager_triggers_manage`, `google_tag_manager_variables_manage`, `google_tag_manager_built_in_variables_manage`, `google_tag_manager_folders_manage`, `google_tag_manager_environments_manage`, `google_tag_manager_versions_manage`, `google_tag_manager_version_headers_manage`, `google_tag_manager_user_permissions_manage`, `google_tag_manager_clients_manage`, `google_tag_manager_templates_manage`, `google_tag_manager_transformations_manage`, `google_tag_manager_zones_manage`, `google_tag_manager_destinations_manage` |
    | Google Search Console | `google_search_console_performance_get`, `google_search_console_sites_list`, `google_search_console_sitemaps_list`, `google_search_console_sitemaps_get`, `google_search_console_sitemaps_submit`, `google_search_console_sitemaps_delete`, `google_search_console_urls_submit` |
    | BigQuery | `bigquery_execute_query`, `bigquery_insert_rows` |
    
    ## Critical rules
    
    1. **GA4 property IDs — arg name differs by tool.** Three patterns: (a) Reporting tools (`run_ga4_report`, `get_property`) take `property_id="properties/123456789"`. (b) Create and list tools (`create_key_event`, `list_key_events`, `create_custom_dimension`, `list_custom_dimensions`, etc.) take `parent="properties/123456789"`. (c) Get/update/delete tools operate on a specific resource and take `name=` with the full resource path (e.g. `"properties/123456789/keyEvents/12345"`). All three need the `properties/` prefix in some form — passing a bare numeric ID silently fails. When in doubt, check the tool's schema for which arg is marked `required`.
    2. **Date ranges are inclusive on both ends.** `start_date="2026-04-01"` and `end_date="2026-04-30"` returns 30 days, not 29. Same for relative dates: `7daysAgo` to `today` is 8 days, not 7.
    3. **GA4 sampling kicks in above ~10M events.** For high-volume properties, the GA4 API silently samples results. If precision matters (board reporting, financial attribution), use the **BigQuery GA4 export** instead — see [`references/bigquery-ga4-export.md`](./references/bigquery-ga4-export.md).
    4. **Conversions in GA4 are "key events".** GA4 renamed "conversions" to "key events" in 2024. The tools reflect this — use `google_analytics_key_events_create` to mark an event as a conversion. Don't get confused by older docs.
    5. **GTM changes need a workspace + version + publish.** Tags / triggers / variables created in a workspace are *not live* until the workspace is committed to a new version and that version is published. Use `google_tag_manager_workspaces_manage` → modify → `google_tag_manager_versions_manage` (create) → publish.
    6. **GSC data has a 2–3 day lag.** Don't query "yesterday" in GSC and expect data — query 3+ days back for stable numbers. GA4 has a 24-48h lag for some metrics.
    7. **Apple Mail Privacy Protection inflates GA4 "engaged" sessions from email.** Don't trust email-driven engagement numbers in GA4 alone — cross-reference with the email provider's own click data.
    
    ## Workflow — pick the right path
    
    The skill covers four distinct jobs. Pick first; the workflows are different.
    
    | The user wants… | Path | Reference |
    | --- | --- | --- |
    | A report ("how did we do last month?") | Phase R | — |
    | To set up tracking ("we need to measure X") | Phase T | [`references/ga4-tracking-plan.md`](./references/ga4-tracking-plan.md) |
    | To audit existing GTM / GA4 ("why is conversion data wonky?") | Phase A | [`references/gtm-audit.md`](./references/gtm-audit.md) |
    | Precise / unsampled / cross-source analysis | Phase B | [`references/bigquery-ga4-export.md`](./references/bigquery-ga4-export.md) |
    
    ### Phase R — Run a report (most common path)
    
    **Warehouse option:** When the workspace has an analytics warehouse connection, discover its tables and schema and query them through `database_query`. Read Warehouse Data Status for the selected connection and report its last successful refresh; do not assume an hourly cache or unsampled data. Otherwise use `google_analytics_ga4_reports_run` for GA4 and `google_search_console_performance_get` for GSC. Keep account IDs, dates and metric definitions explicit.
    
    1. **Confirm the property.** `google_analytics_accounts_list()` → `google_analytics_properties_list(filter="parent:accounts/<account_id>")`. Ask the user to pick if there are multiple. Save the `properties/<id>` for the rest of the conversation.
    2. **Pick the date range.** Always confirm. "Last 30 days" is `start_date="30daysAgo"`, `end_date="yesterday"` (avoid `today` — partial-day data is unstable).
    3. **Pick metrics + dimensions.** Don't blast 12 metrics × 6 dimensions in one report — the result is unreadable. Pick the 2–3 metrics that answer the user's question and the 1–2 dimensions that segment them meaningfully.
    4. **Run the report.**
    
    ```
    google_analytics_ga4_reports_run(
      property_id="properties/123456789",
      start_date="30daysAgo",
      end_date="yesterday",
      metrics=["activeUsers", "sessions", "conversions", "totalRevenue"],
      dimensions=["sessionDefaultChannelGroup", "deviceCategory"],
    )
    ```
    
    5. **Present results as a table.** Always show the raw numbers alongside any interpretation. "Organic search drove 12,400 sessions (+18% MoM)" beats "organic was up."
    6. **One follow-on if the data flags it.** If a metric stands out (e.g., conversion rate dropped 40% on mobile), run *one* targeted follow-up report — don't speculate.
    
    #### Common GA4 metrics & dimensions
    
    The GA4 API uses camelCase names. The most useful:
    
    **Metrics:** `activeUsers`, `sessions`, `screenPageViews`, `bounceRate`, `engagementRate`, `averageSessionDuration`, `conversions`, `eventCount`, `totalRevenue`, `transactions`, `purchaseRevenue`, `userEngagementDuration`.
    
    **Dimensions:** `country`, `city`, `deviceCategory`, `operatingSystem`, `browser`, `sessionDefaultChannelGroup`, `sessionSource`, `sessionMedium`, `sessionCampaignName`, `pagePath`, `eventName`, `date`, `hour`, `landingPage`.
    
    For the full list, the user can browse the [GA4 Data API reference](https://developers.google.com/analytics/devguides/reporting/data/v1/api-schema).
    
    ### Phase T — Set up tracking
    
    The GA4 API can create properties, data streams, key events, custom metrics, and custom dimensions — but it **can't deploy GTM tags into the page**. That step is GTM-side (or hard-coded in the site). The skill workflow:
    
    1. **Tracking plan first.** Define what events you need to fire, where they fire, and which ones are conversions (key events). Full template in [`references/ga4-tracking-plan.md`](./references/ga4-tracking-plan.md).
    2. **GA4-side setup** — create custom dimensions / metrics, mark key events:
    
    ```
    google_analytics_custom_dimensions_create(
      parent="properties/123456789",
      parameter_name="plan_tier",
      display_name="Plan Tier",
      scope="EVENT",
    )
    
    google_analytics_key_events_create(
      parent="properties/123456789",
      event_name="purchase",
      counting_method="ONCE_PER_EVENT",
    )
    ```
    
    3. **GTM-side setup** — create/update tags + triggers + variables in a workspace, then version + publish:
    
    ```
    google_tag_manager_workspaces_manage(operation="create", account_id="...", container_id="...", name="purchase-tracking-v3")
    
    google_tag_manager_tags_manage(operation="create", workspace_path="...", tag_definition={...})
    google_tag_manager_triggers_manage(operation="create", workspace_path="...", trigger_definition={...})
    google_tag_manager_variables_manage(operation="create", workspace_path="...", variable_definition={...})
    
    google_tag_manager_versions_manage(operation="create", workspace_path="...", version_name="purchase-tracking-v3")
    # then publish via the GTM UI or version operation
    ```
    
    4. **Validate** — see Phase A.
    
    ### Phase A — Audit (the existing setup is broken or suspect)
    
    Most "our analytics is wrong" complaints are one of:
    
    | Symptom | Likely cause | How to confirm |
    | --- | --- | --- |
    | Conversion event not appearing in GA4 | Event firing in GTM but not reaching GA4 (wrong measurement ID, blocked by consent gate, ad blocker) | `google_analytics_ga4_reports_run` for `eventName=purchase` over the last 7d → if 0, check GTM |
    | Conversion count wildly off | Event firing on every page (not just confirmation), or duplicate tags | Audit GTM tags via `google_tag_manager_tags_manage(operation="list")`, check for multiple tags firing on the same trigger |
    | Revenue reported differently in GA4 vs the platform of record | Currency mismatch, refund handling, attribution window | Pull both side-by-side, look for refund / currency rows |
    | Suddenly mobile traffic dropped to ~0 | Tag firing only on desktop trigger, or a recent GTM publish broke the mobile container | `google_tag_manager_versions_manage(operation="list")` to find recent publishes, diff with previous version |
    | GSC clicks ≠ GA4 organic sessions | Always different — different definitions. Don't try to reconcile exactly. | Expected; document and move on |
    
    Detailed audit walkthrough in [`references/gtm-audit.md`](./references/gtm-audit.md).
    
    ### Phase B — BigQuery GA4 export (precision / cross-source analysis)
    
    For unsampled data, custom attribution, joining GA4 with order-DB / CRM data, or cohort analysis. Requires the GA4 → BigQuery export to be turned on in the GA4 admin (free for standard properties since 2023).
    
    ```
    bigquery_execute_query(
      query="""
        SELECT
          event_date,
          COUNT(DISTINCT user_pseudo_id) AS users,
          COUNTIF(event_name = 'purchase') AS purchases,
          SUM(IF(event_name = 'purchase', ecommerce.purchase_revenue, 0)) AS revenue
        FROM `your-project.analytics_123456789.events_*`
        WHERE _TABLE_SUFFIX BETWEEN '20260401' AND '20260430'
        GROUP BY event_date
        ORDER BY event_date
      """
    )
    ```
    
    Schema, common queries, and the full attribution-modeling workflow in [`references/bigquery-ga4-export.md`](./references/bigquery-ga4-export.md).
    
    ## Output standards
    
    - **Always present numbers as tables**, not prose. "12,400 sessions, 8.2% bounce, 1.4% conversion" → markdown table.
    - **Always include the date range** in the report header. "Apr 1–30, 2026" — undated numbers are useless.
    - **Always include the property / site** the report came from. Multi-property organizations get burned by this constantly.
    - **Annotate sampling.** If GA4 returns a `samplesReadCount < samplingSpaceSize`, say so explicitly. The user needs to know whether to trust the number for finance / board reporting.
    - **Distinguish "ratio metric" from "summable metric".** Bounce rate, engagement rate, conversion rate are *ratios* and don't sum — present them as one number, not a column total.
    
    ## Reference workflows
    
    | Reference | When to read |
    | --- | --- |
    | [`references/ga4-tracking-plan.md`](./references/ga4-tracking-plan.md) | Designing what to track — recommended event schema, custom dimensions / metrics, key-event mapping, naming conventions |
    | [`references/gtm-audit.md`](./references/gtm-audit.md) | Auditing an existing GTM container — finding duplicate tags, broken triggers, unused variables, missing consent gates |
    | [`references/bigquery-ga4-export.md`](./references/bigquery-ga4-export.md) | Querying the GA4 BigQuery export — schema, common queries (DAU, funnel, attribution, cohort, retention), join patterns |
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related