
Analytics Tracking
- 621 installs
- 23.5k repo stars
- Updated July 17, 2026
- alirezarezvani/claude-skills
analytics-tracking is a Claude agent skill that implements, audits, and debugs GA4, Google Tag Manager, event taxonomy, and conversion tracking so developers ship accurate analytics instrumentation on web products.
About
analytics-tracking is an alirezarezvani/claude-skills skill for setting up, auditing, and debugging analytics tracking implementation across GA4, Google Tag Manager, event taxonomy, conversion tracking, and data quality. Developers reach for analytics-tracking when building a tracking plan from scratch, auditing existing analytics for gaps, debugging missing events, or configuring GTM containers with custom dimensions and UTM handling. Trigger keywords include GA4 setup, GTM, event tracking, conversion tracking, tracking plan, analytics audit, and missing events. The skill explicitly excludes marketing campaign analysis (use campaign-analytics) and in-product BI dashboards (use product-analytics), keeping focus on correct tag implementation and event schema integrity so downstream dashboards reflect real user behavior.
- Implements GA4 and Google Tag Manager from scratch or into existing codebases
- Creates and validates complete event taxonomy and tracking plans
- Audits existing implementations for missing events, duplicate fires, and incorrect parameters
- Debugs broken conversion tracking and custom dimensions
- Enforces consent and data-quality rules before events are sent
Analytics Tracking by the numbers
- 621 all-time installs (skills.sh)
- Ranked #676 of 1,879 Marketing & SEO skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Jul 31, 2026 (Skillselion catalog sync)
npx skills add https://github.com/alirezarezvani/claude-skills --skill analytics-trackingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 621 |
|---|---|
| repo stars | ★ 23.5k |
| Security audit | 1 / 3 scanners passed |
| Last updated | July 17, 2026 |
| Repository | alirezarezvani/claude-skills ↗ |
How do you implement GA4 and GTM event tracking correctly?
Correctly implement, audit, and debug GA4, Google Tag Manager, event taxonomy, and conversion tracking so decision-making data is accurate.
Who is it for?
Developers implementing or fixing GA4 and Google Tag Manager instrumentation who need event taxonomy and conversion tracking to be trustworthy.
Skip if: Developers analyzing paid campaign ROAS, building in-product BI dashboards, or running Amazon marketplace keyword research.
When should I use this skill?
The user mentions GA4 setup, Google Tag Manager, event tracking, conversion tracking, tracking plan, analytics audit, or missing events.
What you get
Tracking plan, GTM container configuration guidance, GA4 event map, and audit notes for missing or broken events.
- tracking plan
- GTM/GA4 configuration guidance
- analytics audit report
Files
Analytics Tracking
You are an expert in analytics implementation. Your goal is to make sure every meaningful action in the customer journey is captured accurately, consistently, and in a way that can actually be used for decisions — not just for the sake of having data.
Bad tracking is worse than no tracking. Duplicate events, missing parameters, unconsented data, and broken conversions lead to decisions made on bad data. This skill is about building it right the first time, or finding what's broken and fixing it.
Before Starting
Check for context first: If .claude/product-marketing-context.md exists, read it before asking questions. Use that context and only ask for what's missing.
Gather this context:
1. Current State
- Do you have GA4 and/or GTM already set up? If so, what's broken or missing?
- What's your tech stack? (React SPA, Next.js, WordPress, custom, etc.)
- Do you have a consent management platform (CMP)? Which one?
- What events are you currently tracking (if any)?
2. Business Context
- What are your primary conversion actions? (signup, purchase, lead form, free trial start)
- What are your key micro-conversions? (pricing page view, feature discovery, demo request)
- Do you run paid campaigns? (Google Ads, Meta, LinkedIn — affects conversion tracking needs)
3. Goals
- Building from scratch, auditing existing, or debugging a specific issue?
- Do you need cross-domain tracking? Multiple properties or subdomains?
- Server-side tagging requirement? (GDPR-sensitive markets, performance concerns)
How This Skill Works
Mode 1: Set Up From Scratch
No analytics in place — we'll build the tracking plan, implement GA4 and GTM, define the event taxonomy, and configure key events.
Start from the generator, then customize:
python3 scripts/tracking_plan_generator.py # embedded sample → full tracking plan
python3 scripts/tracking_plan_generator.py plan.json # your funnel definition
python3 scripts/tracking_plan_generator.py --json # parseable JSON for pipelinesIts output (event taxonomy + parameters + GA4/GTM config checklist) is the working draft for the Event Taxonomy Design section below — review every generated event name against the naming convention before implementing.
Mode 2: Audit Existing Tracking
Tracking exists but you don't trust the data, coverage is incomplete, or you're adding new goals. We'll audit what's there, gap-fill, and clean up.
Mode 3: Debug Tracking Issues
Specific events are missing, conversion numbers don't add up, or GTM preview shows events firing but GA4 isn't recording them. Structured debugging workflow.
---
Event Taxonomy Design
Get this right before touching GA4 or GTM. Retrofitting taxonomy is painful.
Naming Convention
Format: object_action (snake_case, verb at the end)
| ✅ Good | ❌ Bad |
|---|---|
form_submit | submitForm, FormSubmitted, form-submit |
plan_selected | clickPricingPlan, selected_plan, PlanClick |
video_started | videoPlay, StartVideo, VideoStart |
checkout_completed | purchase, buy_complete, checkoutDone |
Rules:
- Always
noun_verbnotverb_noun - Lowercase + underscores only — no camelCase, no hyphens
- Be specific enough to be unambiguous, not so verbose it's a sentence
- Consistent tense:
_started,_completed,_failed(not mix of past/present)
Standard Parameters
Every event should include these where applicable:
| Parameter | Type | Example | Purpose |
|---|---|---|---|
page_location | string | https://app.co/pricing | Auto-captured by GA4 |
page_title | string | Pricing - Acme | Auto-captured by GA4 |
user_id | string | usr_abc123 | Link to your CRM/DB |
plan_name | string | Professional | Segment by plan |
value | number | 99 | Revenue/order value |
currency | string | USD | Required with value |
content_group | string | onboarding | Group pages/flows |
method | string | google_oauth | How (signup method, etc.) |
Event Taxonomy for SaaS
Core funnel events:
visitor_arrived (page view — automatic in GA4)
signup_started (user clicked "Sign up")
signup_completed (account created successfully)
trial_started (free trial began)
onboarding_step_completed (param: step_name, step_number)
feature_activated (param: feature_name)
plan_selected (param: plan_name, billing_period)
checkout_started (param: value, currency, plan_name)
checkout_completed (param: value, currency, transaction_id)
subscription_cancelled (param: cancel_reason, plan_name)Micro-conversion events:
pricing_viewed
demo_requested (param: source)
form_submitted (param: form_name, form_location)
content_downloaded (param: content_name, content_type)
video_started (param: video_title)
video_completed (param: video_title, percent_watched)
chat_opened
help_article_viewed (param: article_name)See references/event-taxonomy-guide.md for the full taxonomy catalog with custom dimension recommendations.
---
GA4 Setup
Data Stream Configuration
1. Create property in GA4 → Admin → Properties → Create 2. Add web data stream with your domain 3. Enhanced Measurement — enable all, then review:
- ✅ Page views (keep)
- ✅ Scrolls (keep)
- ✅ Outbound clicks (keep)
- ✅ Site search (keep if you have search)
- ⚠️ Video engagement (disable if you'll track videos manually — avoid duplicates)
- ⚠️ File downloads (disable if you'll track these in GTM for better parameters)
4. Configure domains — add all subdomains used in your funnel
Custom Events in GA4
For any event not auto-collected, create it in GTM (preferred) or via gtag directly:
Via gtag:
gtag('event', 'signup_completed', {
method: 'email',
user_id: 'usr_abc123',
plan_name: "trial"
});Via GTM data layer (preferred — see GTM section):
window.dataLayer.push({
event: 'signup_completed',
signup_method: 'email',
user_id: 'usr_abc123'
});Key Events Configuration
Mark these events as key events in GA4 → Admin → Key events (GA4 renamed "Conversions" to "Key events" in March 2024 — "conversions" now refers only to Google Ads conversion actions):
signup_completedcheckout_completeddemo_requestedtrial_started(if separate from signup)
Rules:
- Max 30 key events per property — curate, don't mark everything
- Key events are retroactive in GA4 — turning one on applies to 6 months of history
- Don't mark micro-conversions as key events unless you're also optimizing ad campaigns for them
---
Google Tag Manager Setup
Container Structure
GTM Container
├── Tags
│ ├── GA4 Configuration (fires on all pages)
│ ├── GA4 Event — [event_name] (one tag per event)
│ ├── Google Ads Conversion (per conversion action)
│ └── Meta Pixel (if running Meta ads)
├── Triggers
│ ├── All Pages
│ ├── DOM Ready
│ ├── Data Layer Event — [event_name]
│ └── Custom Element Click — [selector]
└── Variables
├── Data Layer Variables (dlv — for each dL key)
├── Constant — GA4 Measurement ID
└── JavaScript Variables (computed values)Tag Patterns for SaaS
Pattern 1: Data Layer Push (most reliable)
Your app pushes to dataLayer → GTM picks it up → sends to GA4.
// In your app code (on event):
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'signup_completed',
signup_method: 'email',
user_id: userId,
plan_name: "trial"
});GTM Tag: GA4 Event
Event Name: {{DLV - event}} OR hardcode "signup_completed"
Parameters:
signup_method: {{DLV - signup_method}}
user_id: {{DLV - user_id}}
plan_name: "dlv-plan-name"
Trigger: Custom Event - "signup_completed"Pattern 2: CSS Selector Click
For events triggered by UI elements without app-level hooks.
GTM Trigger:
Type: Click - All Elements
Conditions: Click Element matches CSS selector [data-track="demo-cta"]
GTM Tag: GA4 Event
Event Name: demo_requested
Parameters:
page_location: {{Page URL}}See references/gtm-patterns.md for full configuration templates.
---
Conversion Tracking: Platform-Specific
Google Ads
1. Create conversion action in Google Ads → Tools → Conversions 2. Import GA4 conversions (recommended — single source of truth) OR use the Google Ads tag 3. Set attribution model: Data-driven (if >50 conversions/month), otherwise Last click 4. Conversion window: 30 days for lead gen, 90 days for high-consideration purchases
Meta (Facebook/Instagram) Pixel
1. Install Meta Pixel base code via GTM 2. Standard events: PageView, Lead, CompleteRegistration, Purchase 3. Conversions API (CAPI) strongly recommended — client-side pixel loses ~30% of conversions due to ad blockers and iOS 4. CAPI requires server-side implementation (Meta's docs or GTM server-side)
---
Cross-Platform Tracking
UTM Strategy
Enforce strict UTM conventions or your channel data becomes noise.
| Parameter | Convention | Example |
|---|---|---|
utm_source | Platform name (lowercase) | google, linkedin, newsletter |
utm_medium | Traffic type | cpc, email, social, organic |
utm_campaign | Campaign ID or name | q1-trial-push, brand-awareness |
utm_content | Ad/creative variant | hero-cta-blue, text-link |
utm_term | Paid keyword | saas-analytics |
Rule: Never tag organic or direct traffic with UTMs. UTMs override GA4's automatic source/medium attribution.
Attribution Windows
| Platform | Default Window | Recommended for SaaS |
|---|---|---|
| GA4 | 30 days | 30-90 days depending on sales cycle |
| Google Ads | 30 days | 30 days (trial), 90 days (enterprise) |
| Meta | 7-day click, 1-day view | 7-day click only |
| 30 days | 30 days |
Cross-Domain Tracking
For funnels that cross domains (e.g., acme.com → app.acme.com):
1. In GA4 → Admin → Data Streams → Configure tag settings → List unwanted referrals → Add both domains 2. In GTM → GA4 Configuration tag → Cross-domain measurement → Add both domains 3. Test: visit domain A, click link to domain B, check GA4 DebugView — session should not restart
---
Data Quality
Deduplication
Events firing twice? Common causes:
- GTM tag + hardcoded gtag both firing
- Enhanced Measurement + custom GTM tag for same event
- SPA router firing pageview on every route change AND GTM page view tag
Fix: Audit GTM Preview for double-fires. Check Network tab in DevTools for duplicate hits.
Bot Filtering
GA4 filters known bots automatically. For internal traffic: 1. GA4 → Admin → Data Filters → Internal Traffic 2. Add your office IPs and developer IPs 3. Enable filter (starts as testing mode — activate it)
Consent Management Impact
Under GDPR/ePrivacy, analytics may require consent. Plan for this:
| Consent Mode setting | Impact |
|---|---|
| No consent mode | Visitors who decline cookies → zero data |
| Basic consent mode | Visitors who decline → zero data |
| Advanced consent mode | Visitors who decline → modeled data (GA4 estimates using consented users) |
Recommendation: Implement Advanced Consent Mode via GTM. Requires CMP integration (Cookiebot, OneTrust, Usercentrics, etc.).
Expected consent rate by region: 60-75% EU, 85-95% US.
---
Proactive Triggers
Surface these without being asked:
- Events firing on every page load → Symptom of misconfigured trigger. Flag: duplicate data inflation.
- No user_id being passed → You can't connect analytics to your CRM or understand cohorts. Flag for fix.
- Conversions not matching GA4 vs Ads → Attribution window mismatch or pixel duplication. Flag for audit.
- No consent mode configured in EU markets → Legal exposure and underreported data. Flag immediately.
- All pages showing as "/(not set)" or generic paths → SPA routing not handled. GA4 is recording wrong pages.
- UTM source showing as "direct" for paid campaigns → UTMs missing or being stripped. Traffic attribution is broken.
---
Output Artifacts
| When you ask for... | You get... |
|---|---|
| "Build a tracking plan" | Event taxonomy table (events + parameters + triggers), GA4 configuration checklist, GTM container structure |
| "Audit my tracking" | Gap analysis vs. standard SaaS funnel, data quality scorecard (0-100), prioritized fix list |
| "Set up GTM" | Tag/trigger/variable configuration for each event, container setup checklist |
| "Debug missing events" | Structured debugging steps using GTM Preview + GA4 DebugView + Network tab |
| "Set up conversion tracking" | Conversion action configuration for GA4 + Google Ads + Meta |
| "Generate tracking plan" | Run python3 scripts/tracking_plan_generator.py [plan.json] [--json] — event taxonomy + GA4/GTM checklist |
---
Communication
All output follows the structured communication standard:
- Bottom line first — what's broken or what needs building before methodology
- What + Why + How — every finding has all three
- Actions have owners and deadlines — no vague "consider implementing"
- Confidence tagging — 🟢 verified / 🟡 estimated / 🔴 assumed
---
Related Skills
- campaign-analytics: Use for analyzing marketing performance and channel ROI. NOT for implementation — use this skill for tracking setup.
- ab-test-setup: Use when designing experiments. NOT for event tracking setup (though this skill's events feed A/B tests).
- analytics-tracking (this skill): covers setup only. For dashboards and reporting, use campaign-analytics.
- seo-audit: Use for technical SEO. NOT for analytics tracking (though both use GA4 data).
- gdpr-dsgvo-expert: Use for GDPR compliance posture. This skill covers consent mode implementation; that skill covers the full compliance framework.
Tracking Debug Playbook
Step-by-step methodology for diagnosing and fixing analytics tracking issues.
---
The Debug Mindset
Analytics bugs are harder than code bugs because: 1. They fail silently — no error thrown, just missing data 2. They often only appear in production 3. They can be caused by timing, consent, ad blockers, or just configuration
Work systematically. Don't guess. Verify at each layer before moving to the next.
---
The Debug Stack (Bottom-Up)
Layer 5: GA4 Reports / DebugView ← what you see
Layer 4: GA4 Data Processing ← where it lands
Layer 3: Network Request ← what was sent
Layer 2: GTM / Tag firing ← what GTM did
Layer 1: dataLayer / App code ← what your app pushedWhen something's missing at Layer 5, start at Layer 1 and verify each layer before going up.
---
Tool Setup
GTM Preview Mode
1. GTM → Preview (top right) 2. Enter your site URL → Connect 3. A blue bar appears at the bottom of your site: "Google Tag Manager" 4. GTM Preview panel opens in a separate tab 5. Perform the action you're debugging 6. Check: did the expected tag fire?
Reading GTM Preview:
- Left panel: events as they occur (Page View, Click, Custom Event, etc.)
- Middle panel: Tags fired / Tags NOT fired for selected event
- Right panel: Variables values at the time of the event
GA4 DebugView
1. GA4 → Admin → DebugView 2. Enable debug mode via:
- GTM: add
debug_mode: trueto your GA4 Event tag parameters - Extension: install "GA Debugger" Chrome extension
- URL parameter: add
?_gl=or use GA4 debug parameter
3. Perform actions on your site 4. Watch events appear in real-time (10-15 second delay)
Chrome DevTools — Network Tab
1. Open DevTools → Network 2. Filter by: collect or google-analytics or analytics 3. Perform the action 4. Look for requests to https://www.google-analytics.com/g/collect 5. Click the request → Payload tab → view parameters
---
Common Issues and Fixes
Issue: Event fires in GTM Preview but not in GA4
Possible causes:
1. Consent mode blocking — user is in denied state
- Check: In GTM Preview, look at Variables →
Analytics Storage— is itdenied? - Fix: Test with consent granted, or implement Advanced Consent Mode
2. Filters blocking data — internal traffic filter is active
- Check: GA4 → Admin → Data Filters — is "Internal Traffic" filter active?
- Fix: Disable filter temporarily, test, then re-enable and exclude your IP correctly
3. Debug mode not enabled — DebugView only shows debug-mode traffic
- Check: Is
debug_mode: trueparameter on the GA4 Event tag? - Fix: Add it, or use the GA4 Debugger Chrome extension
4. Wrong property — you're looking at a different GA4 property
- Check: Confirm Measurement ID in GTM matches the GA4 property you're viewing
- Fix: Compare
G-XXXXXXXXXXin GTM vs. GA4 Data Stream settings
5. Duplicate GA4 configuration tags — two config tags = double sessions + weird data
- Check: GTM → Tags → filter by "GA4 Configuration" — more than one?
- Fix: Delete duplicates, keep one with All Pages trigger
---
Issue: Event not firing in GTM Preview at all
Diagnosis path:
Step 1: Check the trigger
- Is the trigger for this tag listed under the action in GTM Preview?
- If not: the trigger didn't fire
Step 2: Check trigger conditions
- Open the trigger in GTM
- Reproduce the exact scenario step by step
- In GTM Preview, check Variables at the moment the action happened
- Do the variable values match your trigger conditions?
Step 3: dataLayer issue (for Custom Event triggers)
- In GTM Preview → select the relevant event in left panel → Variables tab
- Scroll to find
event— what's the value? - If event name doesn't match trigger exactly: it won't fire (case-sensitive, exact match)
Step 4: Timing issue
- If using "Page View" trigger and element doesn't exist yet: switch to "DOM Ready" or "Window Loaded"
- If SPA: route changes may not trigger "Page View" — use History Change instead
---
Issue: Parameters showing as (not set) or undefined in GA4
Step 1: Verify parameter is in the network request
- DevTools → Network → find GA4 collect request → Payload
- Search for the parameter name (e.g.,
plan_name) - If not there: GTM variable isn't resolving correctly
Step 2: Check the GTM variable
- GTM Preview → find the event → Variables tab
- Find the variable for this parameter (e.g.,
DLV - plan_name) - What's its value? If
undefined: the dataLayer push didn't include this key, or key name is wrong
Step 3: Check dataLayer push in your app code
- DevTools → Console → type:
dataLayer.filter(e => e.event === 'your_event_name') - Inspect the object — is the parameter key present and spelled correctly?
Step 4: Check GA4 custom dimension registration
- Some parameters require a registered custom dimension in GA4 to appear in reports
- GA4 → Admin → Custom Definitions → Custom Dimensions
- If parameter isn't registered here: it'll exist in raw data but won't show in Explore reports
---
Issue: Duplicate events (event fires 2x per action)
Find the duplicates:
- GTM Preview → find the action → how many tags with the same name fired?
- DevTools → Network → filter by
collect→ count hits for the action
Common causes:
1. Enhanced Measurement + manual GTM tag
- e.g., Enhanced Measurement tracks outbound clicks, GTM also has an outbound click tag
- Fix: disable the Enhanced Measurement setting OR remove the GTM tag
2. Two GTM Configuration tags
- Each sends its own hits
- Fix: delete one, keep one
3. SPA router fires pageview + History Change trigger also fires
- Fix: disable Enhanced Measurement pageview, use only History Change tag
4. Event fires on multiple triggers that both match
- Fix: make triggers more specific — add exclusion conditions
---
Issue: Sessions/users look wrong (too high or too low)
Too many sessions:
- Multiple GA4 Configuration tags
- History Change trigger firing + Enhanced Measurement pageview on SPA
- Client ID not persisting (cookie being blocked or cleared)
Too few sessions / users:
- Consent blocking analytics for non-consenting users (expected under strict consent mode)
- Bot filtering too aggressive
- GA4 tags firing on wrong pages only
Sessions reset unexpectedly (user shows as new on every page):
- Cross-domain tracking not configured
- Cookie domain mismatch
- GTM cookie settings incorrect
---
Issue: Conversions not matching between GA4 and Google Ads
Check 1: Attribution window mismatch
- GA4 default: 30-day last click
- Google Ads: check conversion action settings for window
- These legitimately produce different numbers
Check 2: Conversion event names
- In Google Ads → Tools → Conversions → imported from GA4
- Does the linked event name exactly match the GA4 event?
Check 3: Import is linked
- Google Ads → Tools → Linked Accounts → Google Analytics 4
- Is the correct GA4 property linked and synced?
- Sync can take 24-48 hours after changes
Check 4: Enhanced Conversions
- If GA4 uses a user_id or email parameter, Enhanced Conversions can improve matching
- Google Ads → Conversions → Enhanced Conversions for Web → Enable
---
Debug Checklist Template
Use this for any new tracking issue:
[ ] Confirmed exact event name and parameters expected
[ ] Verified app code is pushing to dataLayer (console: dataLayer)
[ ] GTM Preview: trigger fires at correct moment
[ ] GTM Preview: parameters resolve to correct values (not undefined)
[ ] Network: GA4 collect request appears with correct payload
[ ] GA4 DebugView: event appears within 30 seconds
[ ] GA4 DebugView: parameters present and correct
[ ] GA4 Reports: event appears (24-48h delay for standard reports)
[ ] Consent check: tested with analytics consent granted
[ ] Filter check: internal traffic filter not blocking test trafficEvent Taxonomy Guide
Complete reference for naming conventions, event structure, and parameter standards.
---
Why Taxonomy Matters
Analytics data is only as good as its naming consistency. A tracking system with FormSubmit, form_submit, form-submitted, and formSubmitted as four separate "events" is useless for aggregation. One naming standard, enforced from day one, avoids months of cleanup later.
This guide is the reference for that standard.
---
Naming Convention: Full Specification
Format
[object]_[action]Object = the thing being acted upon (noun) Action = what happened (verb, past tense or gerund)
Casing & Characters
| Rule | ✅ Correct | ❌ Wrong |
|---|---|---|
| Lowercase only | video_started | Video_Started, VIDEO_STARTED |
| Underscores only | form_submit | form-submit, formSubmit |
| Noun before verb | plan_selected | selected_plan |
| Past tense or clear state | checkout_completed | checkout_complete, checkoutDone |
| Specific > generic | trial_started | event_triggered |
| Max 4 words | onboarding_step_completed | user_completed_an_onboarding_step_in_the_flow |
Action Vocabulary (Standard Verbs)
Use these verbs consistently — don't invent synonyms:
| Verb | Use for |
|---|---|
_started | Beginning of a multi-step process |
_completed | Successful completion of a process |
_failed | An attempt that errored out |
_submitted | Form or data submission |
_viewed | Passive view of a page, modal, or content |
_clicked | Direct click on a specific element |
_selected | Choosing from options (plan, variant, filter) |
_opened | Modal, drawer, chat window opened |
_closed | Modal, drawer, chat window closed |
_downloaded | File download |
_activated | Feature turned on for first time |
_upgraded | Plan or feature upgrade |
_cancelled | Intentional termination |
_dismissed | User explicitly closed/ignored a prompt |
_searched | Search query submitted |
---
Complete SaaS Event Catalog
Acquisition Events
| Event | Required Parameters | Optional Parameters |
|---|---|---|
ad_clicked | utm_source, utm_campaign | utm_content, utm_term |
landing_page_viewed | page_location, utm_source | variant (A/B) |
pricing_viewed | page_location | referrer_page |
demo_requested | source (page slug or section) | plan_interest |
content_downloaded | content_name, content_type | gated (boolean) |
Acquisition → Registration
| Event | Required Parameters | Optional Parameters |
|---|---|---|
signup_started | — | plan_name, method |
signup_completed | method | user_id, plan_name |
email_verified | — | method |
trial_started | plan_name | trial_length_days |
invitation_accepted | inviter_user_id | plan_name |
Onboarding Events
| Event | Required Parameters | Optional Parameters |
|---|---|---|
onboarding_started | — | onboarding_variant |
onboarding_step_completed | step_name, step_number | time_spent_seconds |
onboarding_completed | steps_total | time_to_complete_seconds |
onboarding_skipped | step_name | step_number |
feature_activated | feature_name | activation_method |
integration_connected | integration_name | integration_type |
team_member_invited | — | invite_method |
Conversion Events
| Event | Required Parameters | Optional Parameters |
|---|---|---|
plan_selected | plan_name, billing_period | previous_plan |
checkout_started | plan_name, value, currency | billing_period |
checkout_completed | plan_name, value, currency, transaction_id | billing_period, coupon_code |
checkout_failed | plan_name, error_reason | value, currency |
upgrade_completed | from_plan, to_plan, value, currency | trigger |
coupon_applied | coupon_code, discount_value | plan_name |
Engagement Events
| Event | Required Parameters | Optional Parameters |
|---|---|---|
feature_used | feature_name | feature_area, usage_count |
search_performed | search_term | results_count, search_area |
filter_applied | filter_name, filter_value | result_count |
export_completed | export_type, export_format | record_count |
report_generated | report_name | date_range |
notification_clicked | notification_type | notification_id |
Retention Events
| Event | Required Parameters | Optional Parameters |
|---|---|---|
subscription_cancelled | cancel_reason | plan_name, save_offer_shown, save_offer_accepted |
save_offer_accepted | offer_type | plan_name, discount_pct |
subscription_paused | pause_duration_days | pause_reason |
subscription_reactivated | — | plan_name, days_since_cancel |
churn_risk_detected | — | risk_score, risk_signals |
Support / Help Events
| Event | Required Parameters | Optional Parameters |
|---|---|---|
help_article_viewed | article_name | article_id, source |
chat_opened | — | page_location, trigger |
support_ticket_submitted | ticket_category | severity |
error_encountered | error_type, error_message | page_location, feature_name |
---
Custom Dimensions & Metrics
GA4 limits: 50 custom dimensions (event-scoped), 25 user-scoped, 50 item-scoped. Prioritize the ones that matter for segmentation.
Recommended User-Scoped Dimensions
| Dimension Name | Parameter | Example Values |
|---|---|---|
| User ID | user_id | usr_abc123 |
| Plan Name | plan_name | starter, professional, enterprise |
| Billing Period | billing_period | monthly, annual |
| Account Created Date | account_created_date | 2024-03-15 |
| Onboarding Completed | onboarding_completed | true, false |
| Company Size | company_size | 1-10, 11-50, 51-200 |
Recommended Event-Scoped Dimensions
| Dimension Name | Parameter | Used In |
|---|---|---|
| Cancel Reason | cancel_reason | subscription_cancelled |
| Feature Name | feature_name | feature_used, feature_activated |
| Content Name | content_name | content_downloaded |
| Signup Method | method | signup_completed |
| Error Type | error_type | error_encountered |
---
Taxonomy Governance
The Tracking Plan Document
Maintain a single tracking plan document (Google Sheet or Notion table) with:
| Column | Values |
|---|---|
| Event Name | e.g., checkout_completed |
| Trigger | "User completes Stripe checkout" |
| Parameters | {value, currency, plan_name, transaction_id} |
| Implemented In | GTM / App code / server |
| Status | Draft / Implemented / Verified |
| Owner | Engineering / Marketing / Product |
Change Protocol
1. New events → add to tracking plan first, get sign-off before implementing 2. Rename events → use a deprecation period (keep old + add new for 30 days, then remove old) 3. Remove events → archive in tracking plan, don't delete — historical data reference 4. Add parameters → non-breaking, implement immediately and update tracking plan 5. Remove parameters → treat as rename (deprecation period)
Versioning
Include schema_version as a parameter on critical events if your taxonomy evolves rapidly:
window.dataLayer.push({
event: 'checkout_completed',
schema_version: 'v2',
value: 99,
currency: 'USD',
// ...
});This allows filtering old vs. new schema during migrations.
GTM Patterns for SaaS
Common Google Tag Manager configurations for SaaS applications.
---
Container Architecture
Naming Convention
Use consistent naming or GTM becomes a black box within 6 months.
Tags: [Platform] - [Event Name] e.g., "GA4 - signup_completed"
Triggers: [Type] - [Description] e.g., "DL Event - signup_completed"
Variables: [Type] - [Parameter Name] e.g., "DLV - plan_name"Required Variables (Create These First)
| Variable Name | Type | Value |
|---|---|---|
CON - GA4 Measurement ID | Constant | G-XXXXXXXXXX |
CON - Environment | Constant | production |
JS - Page Path | Custom JavaScript | function() { return window.location.pathname; } |
JS - User ID | Custom JavaScript | `function() { return window.currentUserId |
GA4 Configuration Tag
One tag, fires on All Pages:
Tag Type: Google Analytics: GA4 Configuration
Measurement ID: {{CON - GA4 Measurement ID}}
Fields to Set:
- user_id: {{JS - User ID}}
Trigger: All Pages---
Pattern Library
Pattern 1: Data Layer Push Event
The most reliable pattern. Your app pushes structured data; GTM listens.
In your application code:
// Call this function on any trackable event
function trackEvent(eventName, parameters) {
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: eventName,
...parameters
});
}
// Example: after successful signup
trackEvent('signup_completed', {
signup_method: 'email',
user_id: newUser.id,
plan_name: 'trial'
});In GTM:
1. Create Data Layer Variables for each parameter:
DLV - signup_method→ Data Layer Variable →signup_methodDLV - user_id→ Data Layer Variable →user_idDLV - plan_name→ Data Layer Variable →plan_name
2. Create Trigger:
- Type: Custom Event
- Event Name:
signup_completed - Name:
DL Event - signup_completed
3. Create Tag:
- Type: Google Analytics: GA4 Event
- Configuration Tag: GA4 Config tag
- Event Name:
signup_completed - Event Parameters:
method:{{DLV - signup_method}}user_id:{{DLV - user_id}}plan_name:{{DLV - plan_name}}- Trigger:
DL Event - signup_completed
---
Pattern 2: Click Event on Specific Element
Use when you can't modify app code and need to track a specific CTA.
GTM Setup:
1. Enable Click - All Elements built-in variables (if not enabled):
- GTM → Variables → Configure → Enable: Click Element, Click ID, Click Classes, Click Text
2. Create Trigger:
- Type: Click - All Elements
- Fire On: Some Clicks
- Conditions:
- Click Element matches CSS selector:
[data-track="demo-cta"]
OR
- Click Text equals "Request a Demo"
- Name:
Click - Demo CTA
3. Create Tag:
- Type: GA4 Event
- Event Name:
demo_requested - Event Parameters:
page_location:{{Page URL}}click_text:{{Click Text}}- Trigger:
Click - Demo CTA
Best practice: Add data-track attributes to important elements in your HTML rather than relying on brittle CSS selectors or text matching.
<button data-track="demo-cta" data-track-source="pricing-hero">
Request a Demo
</button>---
Pattern 3: Form Submission Tracking
Two approaches depending on whether the form submits via JavaScript or full page reload.
For JavaScript-handled forms (AJAX/fetch):
- Use Pattern 1 (dataLayer push) after successful form submission callback
For traditional form submit:
1. Create Trigger:
- Type: Form Submission
- Check Validation: ✅ (only fires if form passes HTML5 validation)
- Enable History Change: ✅ (for SPAs)
- Fire On: Some Forms
- Conditions: Form ID equals
contact-formOR Form Classes containsjs-track-form - Name:
Form Submit - Contact
2. Create Tag:
- Type: GA4 Event
- Event Name:
form_submitted - Parameters:
form_name:contactpage_location:{{Page URL}}- Trigger:
Form Submit - Contact
---
Pattern 4: SPA Page View Tracking
Single-page apps often don't trigger standard page view events on route changes.
Approach A: History Change trigger (simplest)
1. Create Trigger:
- Type: History Change
- Name:
History Change - Route
2. Create Tag:
- Type: GA4 Event
- Event Name:
page_view - Parameters:
page_location:{{Page URL}}page_title:{{Page Title}}- Trigger:
History Change - Route
Important: Disable the default pageview in your GA4 Configuration tag if using this, or you'll get duplicates on initial load.
Approach B: dataLayer push from router (more reliable)
// In your router's navigation handler:
router.afterEach((to, from) => {
window.dataLayer.push({
event: 'page_view',
page_path: to.path,
page_title: document.title
});
});---
Pattern 5: Scroll Depth Tracking
For content engagement measurement:
Option A: Use GA4 Enhanced Measurement (90% depth only)
- Enable in GA4 → Data Streams → Enhanced Measurement → Scrolls
- Fires when user scrolls 90% down the page
- No GTM configuration needed
Option B: Custom milestones via GTM
1. Create Trigger for each depth:
- Type: Scroll Depth
- Vertical Scroll Depths: 25, 50, 75, 100 (percent)
- Enable for: Some Pages → Page Path contains
/blog/ - Name:
Scroll Depth - Blog
2. Create Tag:
- Type: GA4 Event
- Event Name:
content_scrolled - Parameters:
scroll_depth_pct:{{Scroll Depth Threshold}}page_location:{{Page URL}}- Trigger:
Scroll Depth - Blog
---
Pattern 6: Consent Mode Integration
For GDPR compliance — connect your CMP to GTM.
Basic Consent Mode (blocks all when declined):
// In your CMP callback:
window.dataLayer.push({
event: 'cookie_consent_update',
ad_storage: 'denied', // or 'granted'
analytics_storage: 'denied', // or 'granted'
functionality_storage: 'denied',
personalization_storage: 'denied',
security_storage: 'granted' // always granted
});Advanced Consent Mode (modeled data for declined users):
Add to <head> BEFORE GTM loads:
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
// Default all to denied
gtag('consent', 'default', {
ad_storage: 'denied',
analytics_storage: 'denied',
wait_for_update: 500 // ms to wait for CMP to initialize
});Then update when user consents:
gtag('consent', 'update', {
analytics_storage: 'granted'
});---
GTM Version Control
Version Naming Convention
v1.0 - Initial setup: GA4 + core events
v1.1 - Add: checkout tracking
v1.2 - Fix: duplicate pageview on SPA
v2.0 - Overhaul: new event taxonomy + Meta PixelPublishing Protocol
1. Test in GTM Preview mode — verify events fire correctly 2. Test in GA4 DebugView — confirm parameters are captured 3. Test with GTM's "What changed?" diff view 4. Add version notes (what changed + why) 5. Publish to production 6. Verify in GA4 Realtime view post-publish
Environments
Create a staging environment in GTM (Admin → Environments):
- Development: test changes without affecting production
- Staging: validate before publish
- Production: live
Share staging GTM snippet with your dev team so they test against the same container.
---
Common GTM Mistakes
| Mistake | Symptom | Fix |
|---|---|---|
| Tag fires on "All Pages" when it should be scoped | Inflated event counts | Add page conditions to trigger |
| Data Layer Variable path is wrong | Parameter shows as undefined | Use GTM Preview to inspect dataLayer structure |
| GA4 Configuration tag fires multiple times | Duplicate sessions/users | Check all triggers — should be one trigger, "All Pages" |
| Enhanced Measurement conflicts with custom tags | Duplicate outbound click events | Disable conflicting Enhanced Measurement settings |
| Trigger fires before DOM ready | Element not found errors | Change trigger type from "Page View" to "DOM Ready" or "Window Loaded" |
| Form trigger doesn't fire | Form uses AJAX or custom submit | Switch to dataLayer push after submit callback |
#!/usr/bin/env python3
"""Tracking plan generator — produces event taxonomy, GTM config, and GA4 dimension recommendations."""
import json
import sys
from collections import defaultdict
SAMPLE_INPUT = {
"business_type": "saas",
"key_pages": [
{"name": "Homepage", "path": "/"},
{"name": "Pricing", "path": "/pricing"},
{"name": "Signup", "path": "/signup"},
{"name": "Dashboard", "path": "/app/dashboard"},
{"name": "Onboarding", "path": "/app/onboarding"}
],
"conversion_actions": [
{"name": "Signup", "type": "registration", "value": 0},
{"name": "Trial Start", "type": "trial", "value": 0},
{"name": "Subscription Purchase", "type": "purchase", "value": 99},
{"name": "Demo Request", "type": "lead", "value": 0}
],
"paid_channels": ["google_ads", "meta"],
"consent_required": True
}
EVENT_TEMPLATES = {
"saas": {
"acquisition": [
{
"event": "pricing_viewed",
"trigger": "User navigates to /pricing",
"parameters": ["page_location", "utm_source", "referrer_page"],
"priority": "high"
},
{
"event": "demo_requested",
"trigger": "User submits demo request form",
"parameters": ["source", "page_location", "form_name"],
"priority": "high",
"is_conversion": True
},
{
"event": "content_downloaded",
"trigger": "User downloads gated content",
"parameters": ["content_name", "content_type", "gated"],
"priority": "medium"
}
],
"registration": [
{
"event": "signup_started",
"trigger": "User clicks primary signup CTA",
"parameters": ["page_location", "cta_text", "plan_name"],
"priority": "high"
},
{
"event": "signup_completed",
"trigger": "User account successfully created",
"parameters": ["method", "user_id", "plan_name"],
"priority": "critical",
"is_conversion": True
},
{
"event": "trial_started",
"trigger": "Free trial begins",
"parameters": ["plan_name", "trial_length_days", "user_id"],
"priority": "critical",
"is_conversion": True
}
],
"onboarding": [
{
"event": "onboarding_started",
"trigger": "User enters onboarding flow",
"parameters": ["user_id", "onboarding_variant"],
"priority": "high"
},
{
"event": "onboarding_step_completed",
"trigger": "User completes each onboarding step",
"parameters": ["step_name", "step_number", "user_id", "time_spent_seconds"],
"priority": "high"
},
{
"event": "onboarding_completed",
"trigger": "User completes full onboarding",
"parameters": ["steps_total", "user_id", "time_to_complete_seconds"],
"priority": "high"
},
{
"event": "feature_activated",
"trigger": "User activates a key feature for first time",
"parameters": ["feature_name", "user_id", "activation_method"],
"priority": "medium"
}
],
"conversion": [
{
"event": "plan_selected",
"trigger": "User clicks on a pricing plan",
"parameters": ["plan_name", "billing_period", "value"],
"priority": "critical"
},
{
"event": "checkout_started",
"trigger": "User enters checkout flow",
"parameters": ["plan_name", "value", "currency", "billing_period"],
"priority": "critical"
},
{
"event": "checkout_completed",
"trigger": "Payment successfully processed",
"parameters": ["plan_name", "value", "currency", "transaction_id", "billing_period"],
"priority": "critical",
"is_conversion": True
}
],
"retention": [
{
"event": "subscription_cancelled",
"trigger": "User confirms cancellation",
"parameters": ["cancel_reason", "plan_name", "save_offer_shown", "save_offer_accepted"],
"priority": "high"
},
{
"event": "subscription_reactivated",
"trigger": "Cancelled user reactivates",
"parameters": ["plan_name", "days_since_cancel"],
"priority": "high"
}
]
},
"ecommerce": {
"acquisition": [
{
"event": "product_viewed",
"trigger": "User views a product page",
"parameters": ["item_id", "item_name", "item_category", "value"],
"priority": "high"
},
{
"event": "search_performed",
"trigger": "User submits a search query",
"parameters": ["search_term", "results_count"],
"priority": "medium"
}
],
"conversion": [
{
"event": "add_to_cart",
"trigger": "User adds item to cart",
"parameters": ["item_id", "item_name", "value", "currency", "quantity"],
"priority": "critical"
},
{
"event": "checkout_started",
"trigger": "User begins checkout",
"parameters": ["value", "currency", "num_items"],
"priority": "critical"
},
{
"event": "checkout_completed",
"trigger": "Order placed successfully",
"parameters": ["transaction_id", "value", "currency", "tax", "shipping"],
"priority": "critical",
"is_conversion": True
}
]
}
}
CUSTOM_DIMENSIONS = {
"user_scoped": [
{"name": "User ID", "parameter": "user_id", "description": "Internal user identifier"},
{"name": "Plan Name", "parameter": "plan_name", "description": "Current subscription plan"},
{"name": "Billing Period", "parameter": "billing_period", "description": "Monthly or annual"},
{"name": "Signup Method", "parameter": "signup_method", "description": "Email, Google, SSO"},
{"name": "Onboarding Status", "parameter": "onboarding_completed", "description": "Boolean: completed onboarding?"}
],
"event_scoped": [
{"name": "Cancel Reason", "parameter": "cancel_reason", "description": "Exit survey selection"},
{"name": "Feature Name", "parameter": "feature_name", "description": "Feature being used/activated"},
{"name": "Form Name", "parameter": "form_name", "description": "Which form was submitted"},
{"name": "Content Name", "parameter": "content_name", "description": "Downloaded/viewed content"},
{"name": "Error Type", "parameter": "error_type", "description": "Type of error encountered"}
]
}
def generate_tracking_plan(inputs):
biz_type = inputs.get("business_type", "saas")
templates = EVENT_TEMPLATES.get(biz_type, EVENT_TEMPLATES["saas"])
paid = inputs.get("paid_channels", [])
consent = inputs.get("consent_required", False)
conversions = inputs.get("conversion_actions", [])
# Build event taxonomy
all_events = []
for category, events in templates.items():
for ev in events:
all_events.append({**ev, "category": category})
# Add conversion-specific events from input
conversion_events = []
for ca in conversions:
if ca["type"] == "purchase":
for ev in all_events:
if ev["event"] == "checkout_completed":
ev["value_hint"] = ca["value"]
conversion_events.append("checkout_completed")
elif ca["type"] == "registration":
conversion_events.append("signup_completed")
elif ca["type"] == "lead":
conversion_events.append("demo_requested")
elif ca["type"] == "trial":
conversion_events.append("trial_started")
# GTM tag configuration
gtm_tags = []
for ev in all_events:
gtm_tags.append({
"tag_name": f"GA4 - {ev['event']}",
"tag_type": "ga4_event",
"event_name": ev["event"],
"trigger": f"DL Event - {ev['event']}",
"parameters": ev["parameters"],
"priority": ev.get("priority", "medium")
})
# Add platform-specific tags
if "google_ads" in paid:
for ev in all_events:
if ev.get("is_conversion"):
gtm_tags.append({
"tag_name": f"Google Ads - {ev['event']}",
"tag_type": "google_ads_conversion",
"event_name": ev["event"],
"trigger": f"DL Event - {ev['event']}",
"note": "Import from GA4 conversions (preferred) or configure conversion ID"
})
if "meta" in paid:
gtm_tags.append({
"tag_name": "Meta Pixel - Base",
"tag_type": "html_tag",
"trigger": "All Pages",
"note": "Meta base pixel — fires on all pages. Add Standard Events separately."
})
# Consent configuration
consent_config = None
if consent:
consent_config = {
"mode": "advanced",
"defaults": {
"analytics_storage": "denied",
"ad_storage": "denied",
"functionality_storage": "denied"
},
"update_trigger": "cookie_consent_update",
"note": "Implement before GTM loads. Requires CMP integration (Cookiebot, OneTrust, etc.)."
}
return {
"event_taxonomy": [
{
"category": ev["category"],
"event": ev["event"],
"trigger": ev["trigger"],
"parameters": ev["parameters"],
"priority": ev.get("priority", "medium"),
"is_conversion": ev.get("is_conversion", False)
}
for ev in all_events
],
"conversion_events": list(set(conversion_events)),
"gtm_configuration": {
"tags": gtm_tags,
"variable_count": len(set(p for ev in all_events for p in ev["parameters"])),
"trigger_count": len(all_events)
},
"ga4_custom_dimensions": CUSTOM_DIMENSIONS,
"consent_mode": consent_config,
"implementation_order": [
"1. Register custom dimensions in GA4 (Admin > Custom Definitions)",
"2. Set up GTM container structure (variables first, then triggers, then tags)",
"3. Implement dataLayer pushes in application code",
"4. Test each event in GTM Preview + GA4 DebugView",
"5. Mark conversion events in GA4 (Admin > Conversions)",
"6. Link GA4 to Google Ads if running paid search",
"7. Enable internal traffic filter",
"8. Implement consent mode if required"
]
}
def print_report(result, inputs):
print("\n" + "="*65)
print(" TRACKING PLAN GENERATOR")
print("="*65)
print(f"\n📋 BUSINESS TYPE: {inputs.get('business_type', 'saas').upper()}")
events = result["event_taxonomy"]
by_priority = defaultdict(list)
for ev in events:
by_priority[ev["priority"]].append(ev)
print(f"\n📊 EVENT TAXONOMY ({len(events)} events)")
for priority in ["critical", "high", "medium", "low"]:
evs = by_priority.get(priority, [])
if evs:
marker = "🔴" if priority == "critical" else "🟡" if priority == "high" else "⚪"
print(f"\n {marker} {priority.upper()} ({len(evs)} events)")
for ev in evs:
conv = " ← CONVERSION" if ev["is_conversion"] else ""
print(f" {ev['event']}{conv}")
print(f" Params: {', '.join(ev['parameters'][:4])}" +
(f"... +{len(ev['parameters'])-4} more" if len(ev['parameters']) > 4 else ""))
conversions = result["conversion_events"]
print(f"\n🎯 CONVERSION EVENTS ({len(conversions)})")
for ev in conversions:
print(f" • {ev}")
dims = result["ga4_custom_dimensions"]
print(f"\n📐 CUSTOM DIMENSIONS")
print(f" User-scoped ({len(dims['user_scoped'])}): " +
", ".join(d["parameter"] for d in dims["user_scoped"]))
print(f" Event-scoped ({len(dims['event_scoped'])}): " +
", ".join(d["parameter"] for d in dims["event_scoped"]))
gtm = result["gtm_configuration"]
print(f"\n🏷️ GTM CONFIGURATION")
print(f" Tags to create: {len(gtm['tags'])}")
print(f" Triggers to create: {gtm['trigger_count']}")
print(f" Variables to create:{gtm['variable_count']}")
if result["consent_mode"]:
print(f"\n🔒 CONSENT MODE: Advanced (required)")
print(f" Default state: analytics_storage=denied, ad_storage=denied")
print(f"\n📋 IMPLEMENTATION ORDER")
for step in result["implementation_order"]:
print(f" {step}")
print("\n" + "="*65)
print(" Run with --json flag to output full config as JSON")
print("="*65 + "\n")
def main():
import argparse
parser = argparse.ArgumentParser(
description="Tracking plan generator — produces event taxonomy, GTM config, and GA4 dimension recommendations."
)
parser.add_argument(
"input_file", nargs="?", default=None,
help="JSON file with business config (default: run with sample SaaS data)"
)
parser.add_argument(
"--json", action="store_true",
help="Output full config as JSON"
)
args = parser.parse_args()
if args.input_file:
with open(args.input_file) as f:
inputs = json.load(f)
else:
if not args.json:
print("No input file provided. Running with sample data...\n")
inputs = SAMPLE_INPUT
result = generate_tracking_plan(inputs)
print_report(result, inputs)
if args.json:
print(json.dumps(result, indent=2))
if __name__ == "__main__":
main()
Related skills
How it compares
Use analytics-tracking for GA4/GTM implementation and audits; use product-analytics when instrumenting in-app product events rather than marketing site tags.
FAQ
What platforms does analytics-tracking cover?
analytics-tracking covers GA4, Google Tag Manager, event taxonomy design, conversion tracking, custom dimensions, and UTM patterns. It focuses on correct implementation and debugging, not campaign ROAS analysis.
When should analytics-tracking run instead of campaign-analytics?
analytics-tracking should run when building or auditing tag implementation—tracking plans, GTM containers, missing events. campaign-analytics is for analyzing marketing campaign performance after tags already fire correctly.
Can analytics-tracking fix missing GA4 events?
analytics-tracking includes workflows to debug missing events and broken GTM triggers, auditing tag configuration and event taxonomy so GA4 receives the intended conversion and behavioral signals.
Is Analytics Tracking safe to install?
skills.sh reports 1 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.