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_openedsign_up_completedthread_viewedthread_bookmarkedpaywall_shownpurchase_completedsettings_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
sign_up_startedsign_up_completedonboarding_completedfirst_feature_used
Seeing where users drop off in this funnel tells you exactly where to focus improvement.
Feature Adoption
- Event:
feature_used - Breakdown by:
featureproperty - Shows which features get used and which are ignored
Revenue Funnel
paywall_shownpurchase_startedpurchase_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:
typeandendpoint - 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
-
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.
-
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.
-
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.
-
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.