Loading tutorials…
Loading tutorials…
Most teams ship 50 events in week one, then spend month four rewriting them because the names made no sense in retrospect. This walks through an event taxonomy that scales, a property schema that does not drift, and the identify flow that keeps your funnel reports honest.
Who this is forProduct managers and engineers who need PostHog to answer real questions (activation, retention, feature adoption) — not just track pageviews. Especially relevant if you are about to onboard a second engineer and need conventions that survive collaboration.
What you'll need
Step 1
Pick a naming convention (verb_noun, snake_case). List the 10-15 events that map to your North Star metric. Write a one-pager. Get the team to agree.
Decide on naming convention: `verb_noun` in snake_case is the industry standard (`signup_completed`, `checkout_started`, `feature_clicked`). Stick to it.
Use past-tense verbs for completed actions (`signup_completed`), present-tense for moments (`pricing_viewed`). Never mix.
Pick a noun granularity. `checkout_started` is good. `checkout_cart_modal_shown` is too granular and will not survive a UI redesign. Aim for events that describe user intent, not UI structure.
List the 10-15 events that map to your funnel: `app_loaded`, `signup_started`, `signup_completed`, `onboarding_completed`, `first_value_action`, `paid_plan_selected`, `purchase_completed`, etc.
Write the list in a shared Notion doc. Get product, engineering, and the founder to agree before any event ships. This 1-hour conversation prevents 50 hours of cleanup.
Step 2
Properties that apply to multiple events (plan, role, country, signup_source) go into a `super_properties` block. Event-specific properties stay local.
Super properties are sent with EVERY event. Use them for context that always matters: `plan` (free / pro / enterprise), `role` (admin / member / viewer), `country`, `signup_source`.
Set them with `posthog.register({ plan: user.plan, role: user.role, country: user.country })` once on identify. Update with `posthog.register` again when they change.
Person properties are attached to the user, not the event: `email_hash`, `signup_date`, `lifetime_value`. Set with `posthog.identify(userId, { lifetime_value: 199 })`.
Event-specific properties stay local: `checkout_completed` has `amount`, `currency`, `plan_selected`. Do not duplicate person properties into event properties.
Standardize property names. Pick `amount` (not `value`, `price`, `total`) for money. Pick `country` (not `country_code`, `geo`). Document this. Enforce in PR review.
Step 3
On every login: `posthog.identify(userId, { ...userProps })`. On logout: `posthog.reset()`. Never use the email as distinct_id.
On signup completion: `posthog.identify(newUserId, { email_hash, signup_date, signup_source, plan: "free" })`. The first arg becomes the persistent distinct_id.
On login (returning users): `posthog.identify(userId)`. Properties stay current from earlier identifies.
On logout: `posthog.reset()`. This generates a new anonymous distinct_id so the next user does not inherit the session.
NEVER use the email as distinct_id. Use the internal user ID. Emails change. User IDs do not.
For pre-signup attribution: PostHog assigns an anonymous distinct_id automatically. When the user signs up and you call identify, PostHog stitches the anonymous session to the new user automatically.
Step 4
In PostHog → Insights → New Insight → Funnel. Add 4-6 events in sequence. Set conversion window. Pin to a dashboard.
Open PostHog → Insights → New Insight → Funnel.
Add steps: `app_loaded` → `signup_started` → `signup_completed` → `first_value_action` → `purchase_completed`.
Set conversion window: 1 day for transactional flows, 7 or 30 days for SaaS activation flows.
Toggle "Strict mode" if users must complete steps in exact order. Leave off if any-order conversion counts.
Save the funnel. Pin it to a Dashboard (Dashboards → New Dashboard → Activation).
Repeat for retention: Insights → New → Retention → first event = `signup_completed`, returning event = `core_action_performed`. Look at week 1 / week 4 / week 12 retention.
Step 5
Use PostHog Data Management → Event Definitions to verify event volumes weekly. Catch typos, dropped events, and naming drift early.
Open Data Management → Events. Sort by count descending.
Look for typos: `signup_completd`, `signup_complete`, `signupCompleted` all existing alongside `signup_completed` means engineers are not following the convention.
Look for missing events: if `signup_started` fires 1,000/day and `signup_completed` fires 5/day, something is broken — either the funnel is broken or one event is not firing.
Set up an Alert (PostHog → Alerts) for "signup_completed daily count drops 50% week-over-week". Slack notification.
Weekly: scan Event Definitions for events you do not recognize. They are usually typos or experiments left behind. Archive them.
Step 6
Ship a one-page event-tracking guide to the engineering wiki. Reference it in PR reviews. Update quarterly.
Write a one-page doc: event naming convention, super property list, person property list, the 10-15 funnel events, identify/reset rules.
Add a section: "How to add a new event" — propose name in #analytics-events Slack, get one PM + one engineer to approve, then ship.
Reference the doc in PR templates: "If this PR adds analytics events, has the naming been reviewed?"
Review and update quarterly. Archive deprecated events. Rename drifted ones (with a migration period — keep old + new for 30 days, then drop the old).
Common mistakes
Event-naming drift
What goes wrong: Six engineers ship six different events for the same action: `signup`, `signup_done`, `user_signed_up`, `UserSignup`, `signupCompleted`, `signup_completed`. Your funnel reports are wrong for months. Cost: ~2 weeks of cleanup + the product decisions made on bad data.
How to avoid: Pick ONE convention (verb_noun_snake_case past-tense) on day one. Write it in a doc. Reference it in PR reviews. Add a CI lint rule that grep-checks new event names against the convention.
PII in event properties
What goes wrong: Engineer ships `posthog.capture("signup", { email, password_strength })`. PostHog now has plaintext emails forever. At the next GDPR DSAR you spend $3,000+ on engineering to scrub them. PCI / HIPAA audits get expensive.
How to avoid: Hash all PII before sending: `email_hash: sha256(email)`. Set a PR checklist item "no raw PII in event properties". Audit quarterly via Data Management → Properties.
Using email as distinct_id
What goes wrong: User changes email. PostHog now treats them as TWO users. Cohort analysis double-counts them. Retention is over-stated. Subscription matching with Stripe breaks.
How to avoid: Always use your internal user ID (UUID or auto-increment integer) as distinct_id. Email is a person property, not an identity.
Too many events
What goes wrong: You ship 250 events because every engineer adds events for every component. Storage cost is fine; cognitive cost is not. Nobody can find the events that matter. The funnel-building UX becomes unusable.
How to avoid: Cap the active event list at ~50 named custom events. Use autocapture for exploration. Archive unused events quarterly via Data Management → Events → Verify status.
Forgetting `reset()` on logout
What goes wrong: User A logs out, User B logs in on the same browser. PostHog still tags events with User A. Person counts are inflated. Engagement metrics drift. Real users appear to do impossible things (use feature X then never log in to enable it).
How to avoid: `posthog.reset()` in every logout handler. Test by logging out and checking `posthog.get_distinct_id()` in console — should be a new ID starting with a different prefix.
No conversion-window discipline on funnels
What goes wrong: Default funnel conversion window is 14 days. Your sales-cycle is 60 days. You report 12% conversion when the real number is 32%. Decisions get made on the wrong number.
How to avoid: Set conversion window to match your real cycle. Transactional checkout: 1 day. SaaS activation: 7-30 days. B2B with sales-led close: 90 days.
Recap
Done — what's next
How to install PostHog tracking across web, mobile, and server
Read the next tutorial
Hand it off
A good event taxonomy compounds over the entire life of the product. A bad one taxes every report, every experiment, every onboarding of a new analyst. If you would rather have a specialist design the taxonomy AND ship the first 20 events correctly, EverestX matches you with one in 48 hours, from $14-16/hr. A typical setup engagement runs $800-2,000 once, then $200-400/mo for ongoing hygiene.
See specialist rates
~40-60 named custom events for most B2B SaaS products. Below 20 means you are under-instrumented. Above 100 usually means engineers are firing UI-level events that belong to autocapture. The right number is "every event maps to a question you actually ask."
No — that is what autocapture is for. Track named events only for actions tied to product outcomes: signup, activation moments, feature adoption, conversion, churn signals. Let autocapture handle ad-hoc exploration of "what did people click on this page."
Event property describes the SPECIFIC event (a `purchase_completed` has `amount: 99`). Person property describes the USER (signup_date, lifetime_value). Event properties are immutable history; person properties update over time and PostHog's person-on-events mode lets you query against current OR historical person property state.
Ship the new event names alongside the old ones for 30 days. Update all dashboards / funnels / experiments to use the new names. After 30 days, stop firing the old ones. Archive in Data Management. Document the change in your event-tracking guide.
No — PostHog stores events as ingested. You can rename the display name in Data Management → Event Definitions, but the raw event name in the database stays. Always ship a new name and deprecate the old one over a migration window.
PostHog
PostHog has a one-line install — and a hundred ways to get it wrong. This walks through web, Next.js App Router, React Native, and the server SDK (which you need for any event that can't be lost). With the autocapture gotchas that show up at month two.
PostHog
Feature flags are the cheap insurance product teams skip until their first bad deploy. PostHog makes them free — but the targeting rules, SSR caveats, and cleanup discipline are not obvious. This walks through all of it.
PostHog
Running tests is easy. Running tests that produce real decisions is hard. This walks through hypothesis design, sample-size calculation, the PostHog experiment UI, and the 5 statistical mistakes that invalidate 80% of DIY A/B tests.
PostHog
DIY PostHog is the right call up to a point. Then it isn't. This is the honest framework: when the cost of self-managing exceeds the cost of hiring, and how to tell which side you're on.