Analytics
Wire one or more analytics providers through config.analytics, gating cookie-dropping ones behind consent.
config.analytics (in @repo/config) enables web analytics - there is no backend package, scripts render client-side. The presence of a provider sub-key turns it on; there is no single enabled flag.
Providers
Add a provider's sub-key under config.analytics to enable it. The two tiers differ only by GDPR cookie use: consent-required providers wait for the cookie banner, privacy-focused ones load immediately.
| Provider | Key | Config value | Consent required? |
|---|---|---|---|
| Google Analytics 4 | google | { measurementId, ads? } | Yes - waits for consent |
| PostHog | posthog | { publicKey, host } | Yes - waits for consent |
| Umami | umami | { websiteId, host } | No - privacy-focused |
| Plausible | plausible | { domain?, host? } | No - privacy-focused |
| OpenPanel | openpanel | { clientId, trackScreenViews? } | No - privacy-focused |
| DataFast | datafast | { websiteId, domain } | No - privacy-focused |
| Ahrefs | ahrefs | { key } | No - privacy-focused |
| Vercel Analytics | vercel | true | No - privacy-focused |
| Vercel Speed Insights | vercelSpeedInsights | true | No - performance only |
- Shipped default: a generated project ships with no
config.analyticskey, so every provider is absent and off until you add one. - Consent-required (
google,posthog) drop cookies and fire only after the visitor accepts. - Privacy-focused never trigger the consent banner and never wait on it.
vercelandvercelSpeedInsightsare booleans (trueto enable); every other provider takes a config object whose keys are all required unless marked?.vercelSpeedInsightsships Core Web Vitals only - it does not count towardisEnabledor firetrack/trackRevenue/identify.
All analytics keys are public (publishable) - they ship to the browser. Never paste a secret API key into config.
Development behavior
config.analytics.enableInDev controls whether scripts load during local development.
| Key | Type | Type default (key omitted) | Description |
|---|---|---|---|
enableInDev | boolean | false | Load analytics locally so you can test tracking before deploying |
- When omitted, analytics stay dark in development - so a generated config without this key never pollutes real dashboards.
- A generated project ships no
config.analyticskey at all, soenableInDevstaysfalseuntil you add a provider. Set ittruewhile wiring a provider to test tracking locally, thenfalseonce you point providers at your own dashboards.
Tracking calls
One unified useAnalytics() returns the dispatch functions; each fans out to every enabled provider at once (consent-required ones only once consent is granted).
const { track, trackRevenue, identify, reset } = useAnalytics();
track("signup_completed", { plan: "pro" }); // custom event → all providers
trackRevenue(29, "USD", { orderId: "ord_123" }); // revenue event → all providers
identify(userId, { email }); // user → consent-aware subset (see note)
reset(); // clear identity → called automatically on sign-out| Function | Signature | Reaches |
|---|---|---|
track | (event, properties?) | every enabled provider |
trackRevenue | (amount, currency, options?) | every enabled provider; options takes orderId and email |
identify | (userId, traits?) | only providers with a user/profile concept (Umami, OpenPanel, DataFast, GA, PostHog) |
reset | () | providers that store an identity (GA, PostHog, OpenPanel) - clears it |
useAnalytics()runs client-side only; all functions no-op on the server and when no provider is enabled.identifyfires automatically for authenticated users, andresetfires automatically when the session ends (sign-out, expiry) - so the next visitor on the same browser is never attributed to the old account. You only call them yourself for custom flows.- GA receives the pseudonymous
user_idonly - Google's policies prohibit sending PII (email, name) to GA, soidentifytraits are dropped there. - When payments are enabled, the revenue event emits automatically after checkout - you don't call
trackRevenueyourself for purchases. trackRevenueauto-fillsemailfrom the current session for DataFast attribution when you omit it.- Adding more providers needs no code change - the same calls reach all of them.
Built-in events
The boilerplate fires the full conversion funnel automatically - you only add track calls for product-specific events. Names follow GA4 recommended events where one exists; everything flows through the same fan-out, so every enabled provider receives every event.
| Event | Fired when | Properties |
|---|---|---|
sign_up | An account is created (password form, OAuth, magic link) | method |
login | A returning user signs in (any method, incl. passkey and 2FA) | method |
begin_checkout | A checkout is initiated for a plan, product, or credits | type, planId / productId / credits, interval |
purchase | The user returns from a successful checkout | amount, currency, orderId, type |
checkout_cancelled | The user backs out of the provider checkout | - |
waitlist_joined | A visitor joins the waitlist | - |
onboarding_completed | The onboarding form is submitted | marketingOptIn |
organization_created | An organization is created | source (onboarding / settings) |
member_invited | An organization invitation is sent | role |
invitation_accepted | An organization invitation is accepted | - |
api_key_created | An API key is created in developer settings | - |
newsletter_subscribed / newsletter_unsubscribed | The marketing-emails toggle in profile settings changes | - |
contact_form_submitted | The contact form is sent successfully | - |
account_deleted | The user confirms account deletion | - |
- Auth flows that end in a redirect (OAuth, magic link, post-login navigation) are tracked after the redirect: the attempt is marked in
localStorage(15 minute TTL) and resolved on the next authenticated page load, so the event is never lost to the navigation. - For those redirect flows, accounts created within the last 5 minutes resolve to
sign_up; older accounts resolve tologin. - Pageviews are not in this table because each provider's script reports them automatically.
Google Ads
Running Google Ads needs conversions reported back to Google. Add ads under config.analytics.google to wire conversion tracking onto the gtag.js that GA4 already loads - no extra script, same consent gating:
analytics: {
google: {
measurementId: "G-XXXXXXXXXX",
ads: {
conversionId: "AW-XXXXXXXXX",
conversionLabels: {
purchase: "AbC-D_efG-h12_34iJ",
sign_up: "ZyX-w9vU8t76_54sR",
},
},
},
},Both values come from Google Ads: create a conversion action under Goals → Conversions, choose the Google tag setup, and copy the AW- conversion ID plus the per-action label from the snippet it shows.
conversionIdalone registers Google Ads as a second gtag destination: remarketing audiences and the conversion linker (click-ID capture) work immediately. Consent grants already carry the Consent Mode v2 signals (ad_storage,ad_user_data,ad_personalization) Google requires for EEA traffic.conversionLabelsmaps built-in or custom event names to conversion labels; each labeled event additionally fires aconversionping toAW-XXXXXXXXX/LABEL. Thepurchaselabel automatically carriesvalue,currency, andtransaction_id, so value-based Smart Bidding and conversion deduplication work out of the box (enable "use transaction IDs" on the conversion action).- The
adskey is strictly additive: omit it and GA4 behaves exactly as before; omit a label and that event sends no conversion ping.
Prefer not to tag conversions in code? Link GA4 to your Google Ads account and import GA4 key events as conversions instead - that path needs no ads config at all, at the cost of delayed, modeled imports.
Frequently asked questions
Why is there no analytics.enabled flag?
A provider is on exactly when its sub-key is present. Remove the sub-key to disable it; remove config.analytics entirely to disable all analytics.
Which providers force the cookie banner to appear?
Only google and posthog. Under the "auto" banner mode, enabling either surfaces the consent banner for GDPR visitors - privacy-focused providers never do. See cookie consent.
Do my keys leak by being in config?
No - analytics keys are publishable by design and meant to ship to the browser. The warning is only to stop secret keys from ending up there.