Analytics Wiring
You instrument pages with analytics so the operator can measure what's converting. The default is Plausible (privacy-first, $9/mo, no cookie banner needed in most jurisdictions). GA4 is the fallback if a client insists. PostHog is the layer when funnels need event-level tracking.
Standard event taxonomy
Every page in the toolkit fires a consistent set of events. Standardization matters — it lets the operator copy the same dashboards across every client.
| Event |
When |
Required props |
page_view |
Every page load (auto from analytics provider) |
page_path, page_title |
cta_clicked |
Primary or transitional CTA clicked |
cta_id (slug like hero-primary), cta_label, cta_destination |
lead_submitted |
Form submitted successfully |
form_id, form_type (contact, audit-request, lead-magnet, application) |
call_booked |
Cal.com / SavvyCal booking confirmed |
booking_type, service |
pricing_viewed |
Pricing block enters viewport |
none |
funnel_step_advanced |
User advances in a multi-step funnel |
funnel_id, from_step, to_step |
video_played |
VSL or demo video starts |
video_id, video_title |
video_25/50/75/100 |
Video progress milestones |
video_id, progress_pct |
quiz_started / quiz_completed |
Quiz funnel |
quiz_id, result (on completion) |
Don't deviate without a reason. Custom events are added to references/event-taxonomy-extensions.md and reviewed quarterly.
Workflow
Step 1 — load context
Read .agents/agency-context.md §13 (Tech / Domain Inventory). Determine which provider(s) are configured.
If §13 is empty or vague, ask via AskUserQuestion:
"Which analytics setup?"
- "Plausible (recommended — privacy-first, no cookie banner)"
- "GA4 (operator client requirement)"
- "PostHog (event-tracking funnel)"
- "Combo: Plausible + PostHog (funnels)"
Persist the choice to §13.
Step 2 — install the provider
For Plausible:
For GA4:
- Add the GA4 gtag script to Layout.
- Configure events via
gtag('event', '{event_name}', { ...props }).
- Add a cookie consent banner (we ship one in
design-system/primitives/CookieBanner.tsx).
For PostHog:
Step 3 — wire CTA tracking
Every CTA component (Button with as="a", or <a class="cta">) needs an onClick (or analytics-aware Link wrapper) that fires cta_clicked. The Button primitive in design-system/primitives/Button.tsx has this baked in via a trackingId prop.
<Button trackingId="hero-primary" href="/audit">Get my dental SEO audit</Button>
The Button's onClick fires the event with cta_id="hero-primary", cta_label="Get my dental SEO audit", cta_destination="/audit" automatically.
Step 4 — wire form tracking
Every form (<Form> primitive in our shadcn integration) wraps its submit handler to fire lead_submitted on success.
For Resend / Formspree forms:
- Server action returns success → fire
lead_submitted.
- Server action returns error → fire
lead_submission_error.
Step 5 — wire funnel tracking
For multi-step funnels (/new-funnel outputs), every step transition fires funnel_step_advanced.
For VSL pages:
- Wire the video player events (HTML5
play, timeupdate, ended) to video_played + the 25/50/75/100 milestones.
Step 6 — wire conversion goals
Configure the provider's goal/conversion settings:
- Plausible: goals for
lead_submitted, call_booked, cta_clicked (per CTA ID if needed).
- GA4: conversion events for the same.
- PostHog: insights for funnels (page_view → cta_clicked → lead_submitted → call_booked).
Step 7 — verify
Run the test suite:
- Open the dev preview.
- Click each primary CTA — verify
cta_clicked fires with correct props.
- Submit each form with test data — verify
lead_submitted fires.
- (For funnels) walk all steps — verify each
funnel_step_advanced.
- Check the analytics dashboard within 5 min for event ingestion.
If any step fails, fix before passing the page to qa-reviewer.
Hard rules
Privacy & consent
- Plausible doesn't require a cookie banner in most jurisdictions (no PII, no cross-site tracking).
- GA4 requires an explicit consent banner with reject-all option. We ship
CookieBanner primitive that handles GDPR / CCPA defaults.
- PostHog with autocapture on requires consent. Recommend "session recording off" by default; enable only on operator request with explicit consent UX.
- Never store PII in event props (no email, no phone, no name in
lead_submitted props — those go to the CRM, not the analytics tool).
Event-name discipline
- All event names are
snake_case, lowercase.
- Use the standard taxonomy. Don't invent
clicked_button when cta_clicked exists.
- Custom events are added to the extensions doc with rationale.
No tracking on dev / staging
- Use
NODE_ENV checks to skip analytics on dev / staging. Otherwise the operator's local clicks pollute the production analytics.
- Plausible has a "exclude pages" config that does this server-side.
Anti-patterns
- Track every click on the page. Noise. Track CTAs and conversion-relevant events.
- Tracking that double-fires. A common bug — both
onClick and onSubmit firing the same event. Fix at the primitive level.
- PII in event props. Never. Use the CRM for PII; analytics for behavior.
References
references/event-taxonomy-extensions.md — custom events the agency has added.
references/plausible-setup.md — full Plausible install + goals setup.
references/ga4-setup.md — GA4 install + conversion events + GTM if needed.
references/posthog-setup.md — PostHog install + funnels + insights.
references/cookie-consent-implementations.md — GDPR / CCPA / hybrid implementations.
Related skills
agency-context — §13 holds analytics provider IDs.
component-library — provides Button primitive with trackingId prop.
ship-checklist — verifies analytics fire correctly during QA.
1---2name: analytics-wiring3description: When a page or stack needs analytics instrumentation - event tracking, conversion goals, funnel events, consent banners. Triggers - "analytics setup", "GA4", "Plausible", "PostHog", "track conversions", "event tracking", "set up tracking", "conversion goals", "consent banner", "cookie consent". Wires Plausible by default (privacy-first), GA4 if the operator insists, PostHog if event-tracking funnels need it. Standardizes event names across the toolkit.4---5
6# Analytics Wiring
7
8You instrument pages with analytics so the operator can measure what's converting. The default is **Plausible** (privacy-first, $9/mo, no cookie banner needed in most jurisdictions). GA4 is the fallback if a client insists. PostHog is the layer when funnels need event-level tracking.
9
10## Standard event taxonomy
11
12Every page in the toolkit fires a consistent set of events. Standardization matters — it lets the operator copy the same dashboards across every client.
13
14| Event | When | Required props |
15|---|---|---|
16| `page_view` | Every page load (auto from analytics provider) | `page_path`, `page_title` |
17| `cta_clicked` | Primary or transitional CTA clicked | `cta_id` (slug like `hero-primary`), `cta_label`, `cta_destination` |
18| `lead_submitted` | Form submitted successfully | `form_id`, `form_type` (`contact`, `audit-request`, `lead-magnet`, `application`) |
19| `call_booked` | Cal.com / SavvyCal booking confirmed | `booking_type`, `service` |
20| `pricing_viewed` | Pricing block enters viewport | none |
21| `funnel_step_advanced` | User advances in a multi-step funnel | `funnel_id`, `from_step`, `to_step` |
22| `video_played` | VSL or demo video starts | `video_id`, `video_title` |
23| `video_25/50/75/100` | Video progress milestones | `video_id`, `progress_pct` |
24| `quiz_started` / `quiz_completed` | Quiz funnel | `quiz_id`, `result` (on completion) |
25
26Don't deviate without a reason. Custom events are added to `references/event-taxonomy-extensions.md` and reviewed quarterly.
27
28## Workflow
29
30### Step 1 — load context
31
32Read `.agents/agency-context.md` §13 (Tech / Domain Inventory). Determine which provider(s) are configured.
33
34If §13 is empty or vague, ask via `AskUserQuestion`:
35> "Which analytics setup?"
36> - "Plausible (recommended — privacy-first, no cookie banner)"
37> - "GA4 (operator client requirement)"
38> - "PostHog (event-tracking funnel)"
39> - "Combo: Plausible + PostHog (funnels)"
40
41Persist the choice to §13.
42
43### Step 2 — install the provider
44
45For Plausible:
46- Add the Plausible script to the `Layout` component:
47 ```html
48 <script defer data-domain="{domain}" src="https://plausible.io/js/script.js"></script>
49 ```
50- Use the custom-events extension for our event taxonomy:
51 ```html
52 <script defer data-domain="{domain}" src="https://plausible.io/js/script.tagged-events.js"></script>
53 ```
54- Verify install via Plausible's installation checker.
55
56For GA4:
57- Add the GA4 gtag script to Layout.
58- Configure events via `gtag('event', '{event_name}', { ...props })`.
59- Add a cookie consent banner (we ship one in `design-system/primitives/CookieBanner.tsx`).
60
61For PostHog:
62- Install the JS SDK: `npm i posthog-js`.
63- Initialize in a client-only script in Layout:
64 ```ts
65 import posthog from 'posthog-js';
66 if (typeof window !== 'undefined') {
67 posthog.init('{PUBLIC_POSTHOG_KEY}', { api_host: 'https://app.posthog.com' });
68 }
69 ```
70- Wire `posthog.capture('{event_name}', props)` to our event taxonomy.
71
72### Step 3 — wire CTA tracking
73
74Every CTA component (`Button` with `as="a"`, or `<a class="cta">`) needs an `onClick` (or analytics-aware Link wrapper) that fires `cta_clicked`. The `Button` primitive in `design-system/primitives/Button.tsx` has this baked in via a `trackingId` prop.
75
76```tsx
77<Button trackingId="hero-primary" href="/audit">Get my dental SEO audit</Button>
78```
79
80The Button's onClick fires the event with `cta_id="hero-primary", cta_label="Get my dental SEO audit", cta_destination="/audit"` automatically.
81
82### Step 4 — wire form tracking
83
84Every form (`<Form>` primitive in our shadcn integration) wraps its submit handler to fire `lead_submitted` on success.
85
86For Resend / Formspree forms:
87- Server action returns success → fire `lead_submitted`.
88- Server action returns error → fire `lead_submission_error`.
89
90### Step 5 — wire funnel tracking
91
92For multi-step funnels (`/new-funnel` outputs), every step transition fires `funnel_step_advanced`.
93
94For VSL pages:
95- Wire the video player events (HTML5 `play`, `timeupdate`, `ended`) to `video_played` + the 25/50/75/100 milestones.
96
97### Step 6 — wire conversion goals
98
99Configure the provider's goal/conversion settings:
100
101- Plausible: goals for `lead_submitted`, `call_booked`, `cta_clicked` (per CTA ID if needed).
102- GA4: conversion events for the same.
103- PostHog: insights for funnels (page_view → cta_clicked → lead_submitted → call_booked).
104
105### Step 7 — verify
106
107Run the test suite:
1081. Open the dev preview.
1092. Click each primary CTA — verify `cta_clicked` fires with correct props.
1103. Submit each form with test data — verify `lead_submitted` fires.
1114. (For funnels) walk all steps — verify each `funnel_step_advanced`.
1125. Check the analytics dashboard within 5 min for event ingestion.
113
114If any step fails, fix before passing the page to `qa-reviewer`.
115
116---
117
118## Hard rules
119
120### Privacy & consent
121
122- **Plausible** doesn't require a cookie banner in most jurisdictions (no PII, no cross-site tracking).
123- **GA4** requires an explicit consent banner with reject-all option. We ship `CookieBanner` primitive that handles GDPR / CCPA defaults.
124- **PostHog** with autocapture on requires consent. Recommend "session recording off" by default; enable only on operator request with explicit consent UX.
125- Never store PII in event props (no email, no phone, no name in `lead_submitted` props — those go to the CRM, not the analytics tool).
126
127### Event-name discipline
128
129- All event names are `snake_case`, lowercase.
130- Use the standard taxonomy. Don't invent `clicked_button` when `cta_clicked` exists.
131- Custom events are added to the extensions doc with rationale.
132
133### No tracking on dev / staging
134
135- Use `NODE_ENV` checks to skip analytics on dev / staging. Otherwise the operator's local clicks pollute the production analytics.
136- Plausible has a "exclude pages" config that does this server-side.
137
138## Anti-patterns
139
140- **Track every click on the page.** Noise. Track CTAs and conversion-relevant events.
141- **Tracking that double-fires.** A common bug — both `onClick` and `onSubmit` firing the same event. Fix at the primitive level.
142- **PII in event props.** Never. Use the CRM for PII; analytics for behavior.
143
144## References
145
146- `references/event-taxonomy-extensions.md` — custom events the agency has added.
147- `references/plausible-setup.md` — full Plausible install + goals setup.
148- `references/ga4-setup.md` — GA4 install + conversion events + GTM if needed.
149- `references/posthog-setup.md` — PostHog install + funnels + insights.
150- `references/cookie-consent-implementations.md` — GDPR / CCPA / hybrid implementations.
151
152## Related skills
153
154- `agency-context` — §13 holds analytics provider IDs.
155- `component-library` — provides `Button` primitive with `trackingId` prop.
156- `ship-checklist` — verifies analytics fire correctly during QA.