Goodspeed
Goodspeed
← Blog
Technicalby Goodspeed Team

Setting Up PostHog Analytics in Mobile Apps

A practical guide to PostHog analytics in React Native, covering event taxonomy, custom properties, dashboards, and privacy-friendly tracking.

Why PostHog for Mobile

Most analytics tools are built for web. PostHog started there too, but it's become one of the best options for mobile apps. Here's why:

  • Self-hostable or cloud: Choose your data residency
  • Event-based: Track anything, not just page views
  • Feature flags: Roll out features to a percentage of users
  • Session replay: Watch how users navigate your app (web only for now, mobile coming)
  • Generous free tier: 1 million events per month

We track analytics in every app we build. PostHog is our default choice. Here's exactly how to set it up in React Native.

Installation

npx expo install posthog-react-native

Initialization (The Right Way)

The most common mistake with PostHog in React Native is initializing with an empty API key. If the EXPO_PUBLIC_POSTHOG_API_KEY environment variable is not set (common in development), creating a PostHog instance with an empty string will crash your app.

// lib/posthog.ts
import PostHog from 'posthog-react-native';

const apiKey = process.env.EXPO_PUBLIC_POSTHOG_API_KEY;
const host = process.env.EXPO_PUBLIC_POSTHOG_HOST || 'https://us.i.posthog.com';

// Only create the client if we have a valid API key
export const posthog = apiKey
  ? new PostHog(apiKey, { host })
  : null;

Guard every analytics call:

export function capture(event: string, properties?: Record<string, unknown>) {
  if (!posthog) return;
  posthog.capture(event, properties);
}

export function identify(userId: string, properties?: Record<string, unknown>) {
  if (!posthog) return;
  posthog.identify(userId, properties);
}

export function reset() {
  if (!posthog) return;
  posthog.reset();
}

This pattern ensures your app works in any environment, with or without analytics configured.

Designing Your Event Taxonomy

Before you start tracking, plan your events. A messy event taxonomy is worse than no analytics at all. You'll spend more time cleaning data than learning from it.

The Event Naming Convention

Use object_action format with snake_case:

  • app_opened
  • sign_up_completed
  • thread_viewed
  • thread_bookmarked
  • paywall_shown
  • purchase_completed
  • settings_theme_changed

Not:

  • AppOpened (inconsistent with PostHog conventions)
  • click_button_3 (meaningless without context)
  • user did a thing (not queryable)

Essential Events for Any Mobile App

Every app should track these at minimum:

// App lifecycle
capture('app_opened');
capture('app_backgrounded');

// Authentication
capture('sign_up_started', { method: 'email' });
capture('sign_up_completed', { method: 'email' });
capture('sign_in_completed', { method: 'google' });
capture('sign_out');

// Core feature usage
capture('feature_used', { feature: 'thread_analysis', screen: 'dashboard' });

// Monetization
capture('paywall_shown', { trigger: 'feature_gate', feature: 'ai_analysis' });
capture('purchase_started', { product: 'premium_monthly' });
capture('purchase_completed', { product: 'premium_monthly', price: 4.99 });

// Errors
capture('error_occurred', { type: 'api_error', endpoint: '/threads', status: 500 });

Custom Properties

Attach context to every event. Properties answer "what happened" and "where":

capture('thread_viewed', {
  thread_id: thread.id,
  subreddit: thread.subreddit,
  score: thread.score,
  source: 'trending_feed', // How did they get here?
});

Good properties:

  • Identify the object (ID, name, category)
  • Describe the context (screen, source, trigger)
  • Include relevant metrics (score, count, duration)

Avoid:

  • Personal identifiable information (email, name) unless you've got consent
  • High-cardinality free text (full URLs, user input)
  • Redundant data (don't include user ID in every event; PostHog handles this)

User Identification

Link anonymous events to authenticated users:

// After sign-in or sign-up
identify(user.id, {
  email: user.email,
  plan: 'free', // or 'premium'
  signed_up_at: user.created_at,
  platform: Platform.OS,
});

Call identify once after authentication. PostHog will associate all previous anonymous events with this user (if you're using the same device).

When the user signs out:

reset(); // Clears the identified user, creates a new anonymous ID

Building a useAnalytics Hook

Wrap PostHog in a custom hook that makes tracking easy across your app:

// hooks/useAnalytics.ts
import { useCallback } from 'react';
import { capture, identify, reset } from '../lib/posthog';

export function useAnalytics() {
  const trackEvent = useCallback((event: string, properties?: Record<string, unknown>) => {
    capture(event, {
      ...properties,
      timestamp: new Date().toISOString(),
    });
  }, []);

  const trackScreen = useCallback((screenName: string) => {
    capture('screen_viewed', { screen: screenName });
  }, []);

  const identifyUser = useCallback((userId: string, traits?: Record<string, unknown>) => {
    identify(userId, traits);
  }, []);

  const resetUser = useCallback(() => {
    reset();
  }, []);

  return { trackEvent, trackScreen, identifyUser, resetUser };
}

Setting Up Dashboards

In PostHog, create a dashboard for your app with these panels:

Daily Active Users

  • Event: app_opened
  • Aggregation: Unique users per day
  • This is your north star metric

Signup Funnel

  1. sign_up_started
  2. sign_up_completed
  3. onboarding_completed
  4. first_feature_used

Seeing where users drop off in this funnel tells you exactly where to focus improvement.

Feature Adoption

  • Event: feature_used
  • Breakdown by: feature property
  • Shows which features get used and which are ignored

Revenue Funnel

  1. paywall_shown
  2. purchase_started
  3. purchase_completed

The conversion rate from paywall shown to purchase completed tells you if your pricing and positioning work.

Error Tracking

  • Event: error_occurred
  • Breakdown by: type and endpoint
  • Alert if the count exceeds a threshold

Feature Flags

PostHog feature flags let you roll out features gradually:

import { useFeatureFlag } from 'posthog-react-native';

function MyComponent() {
  const showNewDashboard = useFeatureFlag('new-dashboard-v2');

  if (showNewDashboard) {
    return <NewDashboard />;
  }
  return <OldDashboard />;
}

Use feature flags for:

  • Gradual rollouts (10% of users, then 50%, then 100%)
  • A/B testing different UI variants
  • Kill switches for broken features
  • Beta access for specific user segments

Privacy Considerations

GDPR Compliance

Don't track until the user consents. PostHog's optOut flag makes this straightforward:

// Before consent
posthog?.optOut();

// After user grants consent
posthog?.optIn();

Data Minimization

Only track what you need. Don't capture personal information in event properties unless it's necessary for analysis. PostHog lets you configure which properties to redact on the server side.

Retention Policies

Set data retention in PostHog's project settings. You probably don't need event data from 3 years ago. 12 months is a reasonable default.

Common Mistakes

  1. Tracking too many events. Start with 10-15 core events. Add more as questions arise. You can always add events later; removing noise from existing data is harder.

  2. Not tracking enough context. An event without properties is nearly useless. "button_clicked" tells you nothing. "purchase_started with product=premium_yearly, trigger=upgrade_banner, screen=settings" tells you everything.

  3. Forgetting to test in production. Verify events are flowing in the PostHog dashboard after your production deploy. Development and production often have different API keys.

  4. Ignoring the data. The best analytics setup is worthless if nobody looks at the dashboards. Set a weekly reminder to review your core metrics.

For more on building production apps with proper analytics, check out our tech stack and the complete build pipeline.

Subscribe to The Signal

The top 5 app ideas every week.

Ready to build?

Score your first idea free. See the pipeline in action.