How to instrument product analytics properly the first time

TL;DR

Getting product analytics right means writing a tracking plan first, instrumenting events consistently, and validating everything in a debugger before anything goes live. The full setup takes one to two focused days. You need an analytics tool like Mixpanel or PostHog, access to your codebase (or a developer to pair with), and a shared doc to hold your tracking plan.

What You Need Before You Start

  • Access to your product’s frontend and backend codebase, or a developer who can pair with you for a day
  • An analytics account: Mixpanel free tier supports up to 20M monthly events, Amplitude free tier covers 10M events per month, PostHog open-source is free to self-host
  • Node.js 18+ or Python 3.10+ if you are setting up a server-side SDK
  • A Google Sheet or Notion doc for your tracking plan
  • Optional: Segment if you want a customer data platform layer between your product and your analytics tools (free up to 1,000 monthly tracked users)
  • Optional: a staging environment so you can validate events without polluting production data

Step 1: Write Your Tracking Plan Before Touching Any Code

This is the step most teams skip, and it is the reason most analytics setups become a mess within six months.

A tracking plan is a spreadsheet that lists every event you plan to fire, the properties attached to each event, who triggers it, and why. It sounds boring. It saves you from having Button Clicked, button_clicked, and buttonClicked all living in the same workspace representing the same action three months from now.

Open a Google Sheet and create columns for: Event Name, Trigger (what user action fires it), Properties (key-value pairs), Platform (web, iOS, Android, server), Owner, and Status.

Start small. Write down ten events that represent your core user journey. For a SaaS product that might be: Signed Up, Onboarding Completed, Feature Used, Upgrade Clicked, Subscription Cancelled.

You can find a ready-made template at /analytics-tracking-plan-template/ that maps to the steps in this guide.

You should now see: a single tab in a shared doc with at least five events defined, each with at least three named properties and a clear trigger description.

Step 2: Choose Your Analytics Tool and Create Your Project

Pick one tool before you instrument anything. Using two tools from day one without a CDP in between means writing every event twice and reconciling two different user graphs forever.

If you are pre-product-market-fit and want something free and fast, PostHog Cloud or Mixpanel free tier cover everything you need at early scale. If you have paying users already and need cohort analysis plus raw data exports, Amplitude’s Starter plan at $0 is a reasonable starting point. If you expect to feed data to multiple destinations later, add Segment as your routing layer first.

Create a project in your chosen tool. Name it after your environment, not your company. Use MyApp - Production and MyApp - Staging as separate projects. This is a five-minute setup but prevents weeks of debugging later when test events from your QA runs inflate your production funnels.

Go to Settings > Project and copy your Write Key or Project Token. You will need it in Step 3.

You should now see: two projects in your analytics workspace, one for staging and one for production, each with a distinct API key.

Step 3: Install the SDK in Your Codebase

For a JavaScript or React frontend, install the Mixpanel browser SDK via npm:

npm install mixpanel-browser

Initialize it at the root of your app before any route renders:

import mixpanel from 'mixpanel-browser';

mixpanel.init('YOUR_PROJECT_TOKEN', {
  debug: process.env.NODE_ENV !== 'production',
  track_pageviews: true,
  persistence: 'localStorage',
});

If you chose PostHog, the init call is nearly identical:

import posthog from 'posthog-js';

posthog.init('YOUR_API_KEY', {
  api_host: 'https://app.posthog.com',
  loaded: (posthog) => {
    if (process.env.NODE_ENV === 'development') posthog.debug();
  },
});

For server-side events in Node.js, install the server SDK separately. Server-side tracking is where you fire events for things that happen outside the browser: payment completions, API webhook triggers, scheduled job outcomes.

You should now see: your browser console showing outgoing network requests to your analytics provider’s endpoint when you load the app locally.

Step 4: Define Your Event Taxonomy and Enforce It

Taxonomy is just naming conventions. Decide on one format before the second engineer ever writes an analytics call.

The most readable format for product analytics is Object-Action: Signup Form Submitted, Dashboard Viewed, Export Button Clicked. All words capitalized. Object first, then the past-tense verb. This matches how you naturally think about user behavior and makes event lists scannable.

Write these rules in your tracking plan doc so any engineer can follow them without asking you:

  • Past tense verbs only (Clicked not Click, Submitted not Submit)
  • No abbreviations (Subscription not Sub, Organization not Org)
  • No generic events (Dashboard Viewed is useful, Page Loaded tells you nothing)
  • Properties use snake_case: plan_type, user_id, button_label

Add a Global Properties tab to your tracking plan. These are properties that attach to every single event: user_id, session_id, plan_type, app_version, platform. You will set these once via super properties in Step 8, not manually on every track call.

You should now see: a taxonomy reference in your tracking plan that a new engineer could follow on their first day without a thirty-minute onboarding call from you.

Step 5: Fire Your First track() Call

Pick the simplest event on your list and instrument it. Do not try to add ten events in one pull request. One event fully instrumented with all its properties is worth more than ten events with missing data.

Here is what a complete track call looks like for a signup event:

mixpanel.track('Signup Form Submitted', {
  signup_method: 'email',       // 'email', 'google', 'github'
  plan_selected: 'free',
  referrer: document.referrer,
  ab_test_variant: getABVariant('onboarding-v2'),
});

Notice what is included. The event name follows your taxonomy. Every property answers a specific question a PM might have: how did they sign up, what plan, where did they come from, which experiment variant were they seeing. Do not add a user_id here manually. Step 7 handles user identification the right way.

You should now see: the event appearing in your analytics tool’s live event stream or activity feed within a few seconds of triggering the action in your local browser.

Step 6: Validate Events in the Debugger

Never ship analytics changes without validating them first. Every major analytics tool has a built-in debugger and you should use it before every merge.

In Mixpanel, go to Events > Live View. Filter by your staging project and trigger the action on your local machine. In PostHog, use the Activity tab or the browser console debug mode. In Amplitude, the Ingestion Debugger is under Data > Sources.

Check three things for every event:

  1. The event name matches your tracking plan exactly (case, spacing, everything)
  2. All expected properties are present with non-null values
  3. The timestamp is accurate

A quick browser console check for Mixpanel:

// Run in browser console to inspect current super properties
console.log(mixpanel.cookie.props);

If a property shows as null or undefined, find the source before merging. A null property on day one becomes a broken filter in every dashboard you build for the next year.

You should now see: every event you fire during QA appearing in the live stream with correct property values and zero null fields for required properties.

Step 7: Identify Users Properly

Anonymous events are useful. But the moment a user signs up or logs in, you need to connect their actions to a stable identity.

Call identify() immediately after a successful login or signup. Pass your internal user ID, not their email address. Email addresses change when users update their account settings. Internal IDs do not.

// Call this on login success or signup success
mixpanel.identify(user.id);

mixpanel.people.set({
  $email: user.email,
  $name: user.fullName,
  plan_type: user.plan,
  created_at: user.createdAt,
  company_id: user.companyId,
});

If your tool supports alias calls for linking anonymous pre-signup behavior to the newly identified user, call alias() before identify() on the signup event only. Do not call alias on every login. That is a specific mistake that corrupts your user graph by merging unrelated anonymous sessions.

You should now see: identified users appearing in your analytics tool’s People or Users section with profile properties populated.

Step 8: Add Super Properties for Global Context

Super properties are key-value pairs that attach to every future event automatically. You set them once per session and the SDK appends them without you touching individual track calls.

Use super properties for things that describe the user’s current state: plan type, role, app version, experiment assignments. This way you never forget to pass plan_type to an individual track call because you were in a hurry.

mixpanel.register({
  plan_type: user.plan,      // 'free', 'pro', 'enterprise'
  app_version: '2.4.1',
  user_role: user.role,      // 'admin', 'member', 'viewer'
});

Set this right after your identify() call on login. For properties that should survive across sessions (like the original acquisition channel), use register_once() instead of register(). This writes the value only if it does not already exist, so it will not overwrite the original attribution on subsequent logins.

You should now see: every new event in your live stream automatically carrying the global properties without you adding them to each track call manually.

Step 9: Build Your First Funnel Report

With events flowing and users identified, build one funnel report to confirm the instrumentation is telling a coherent story.

In Mixpanel or Amplitude, create a funnel with three to five steps that map your core activation flow. For a SaaS product: Signed Up > Onboarding Completed > First Feature Used > Upgrade Clicked.

Look at the conversion percentages between each step. If step 2 shows more users than step 1, you have a tracking problem. Users cannot complete onboarding without signing up first. That impossible number means you have an event firing twice or your identify call is misconfigured.

A 40 to 70 percent drop between steps is normal and expected. A 0 percent or 200 percent figure is a bug, not a product insight.

A healthy funnel report at this stage validates your instrumentation more reliably than any console check you ran in Step 6.

You should now see: a funnel with realistic monotonically decreasing user counts at each step and no impossible conversions.

Step 10: Document Everything and Set Up Change Control

Your tracking plan is not a one-time document. It is a living reference that every engineer touches when they build a new feature.

Add a Status column with values: Planned, Implemented, Deprecated. Mark every event you just shipped as Implemented with the implementation date and the pull request link.

Set one firm rule going forward: no new analytics event ships without a tracking plan row. Add this as a checklist item in your PR template:

- [ ] Tracking plan updated for new or changed events
- [ ] Events validated in staging debugger
- [ ] Properties match taxonomy (Object-Action naming, snake_case properties)
- [ ] No null values for required properties in debugger test

You should now see: a tracking plan that accurately reflects your production instrumentation and a PR process that prevents future taxonomy drift.

Common Mistakes To Avoid

  • Tracking everything from day one. Fifty events with patchy data is worse than ten events with complete data. Instrument the core funnel first and expand deliberately.
  • Using email as the user identifier. Emails change. Internal numeric or UUID user IDs do not. Build your identity graph on the stable one.
  • Calling alias() on every login. The alias call merges an anonymous pre-signup session with a newly identified user. It fires once, on signup. On every subsequent login you call identify() only.
  • Not separating staging and production projects. QA events mixed into production data corrupt your metrics and make every dashboard number unreliable.
  • Naming events inconsistently. Without a PR checklist enforcing your taxonomy, you will end up with button_clicked, Button Click, and clicked_button for the same action within a quarter.
  • Skipping debugger validation before merging. Broken events do not announce themselves. They silently send null properties for months until someone notices a funnel conversion rate that makes no sense.

When To Level Up

This setup comfortably handles most products up to around 50,000 monthly active users without straining your free tier or requiring a dedicated data engineering hire.

Past that point, you run into three distinct walls. First, event volume. Mixpanel’s free plan covers 20M events per month, but once your engineering team adds more tracking to new features, you burn through that faster than the roadmap predicts.

Second, governance. When you have multiple product squads each adding events independently, a Google Sheet tracking plan stops scaling. You need a proper data catalog or a CDP with schema enforcement so teams do not overwrite each other’s event definitions.

Third, identity resolution across platforms. If your users move between web, native mobile, and server-triggered workflows, stitching those sessions together without a dedicated identity layer gets painful and produces metrics no one trusts.

When you hit any of these walls, it is time to look at Segment for event routing or a warehouse-first analytics setup using dbt and BigQuery. You can also compare Mixpanel vs. Amplitude at different growth stages to decide whether to stay on your current tool or migrate. Start the research at /category/data-analysis/ for a full comparison of tools at that scale.

Frequently Asked Questions

Do I need a developer to instrument analytics?
For most web products, yes. The tracking plan and event taxonomy are PM work, but writing the actual track() calls requires someone who can touch the codebase. PostHog has a no-code autocapture feature that gets you basic click and pageview data without engineering help, but it does not replace intentional instrumentation for custom business events.

How many events should I start with?
Start with ten events that map to your core user journey: signup, key onboarding steps, the first use of your main feature, an upgrade intent signal, and a churn signal. You can always add more events later. You cannot retroactively clean up inconsistent historical data once it is in your analytics tool.

What is the difference between track() and page tracking?
track() fires for explicit user actions like button clicks, form submissions, or feature usage. Page tracking fires automatically on route changes to record navigation. Use both, but do not rely on pageviews alone to understand in-product behavior. Pageviews tell you where users went, not what they did.

Can I use Google Analytics 4 for product analytics?
GA4 works well for marketing attribution and traffic analysis. For in-product behavior like feature adoption rates, retention cohorts, and user-level funnel analysis, it is the wrong tool. GA4’s user-level data is sampled at scale and its funnel builder is not designed for product metrics the way Mixpanel or Amplitude are.

How do I handle GDPR and data privacy compliance?
Before you identify users, confirm your privacy policy covers behavioral tracking. Use opt_out_tracking() calls for users in GDPR regions who have not given consent. PostHog self-hosted is the most straightforward path to keeping all event data on your own infrastructure if regulatory compliance is a hard requirement from your legal team.

Bottom Line

Instrumenting product analytics properly is a two-day project if you do it in the right order. Write the tracking plan before any code is touched. Pick one analytics tool and create separate staging and production projects immediately. Install the SDK, define a naming convention you can actually enforce via a PR checklist, and validate every event in the debugger before it merges. Identify users with your internal ID rather than their email, set global context once via super properties, and build a single funnel report to confirm the data is coherent and monotonically decreasing. The whole system only holds long-term if your tracking plan stays updated and your merge process enforces it. Done right, you will have clean, queryable data from day one rather than spending six months backfilling and correcting a broken event schema. For more tools and frameworks to build on top of this foundation, browse the full guide collection at /category/data-analysis/.