DEV Community

kanta13jp1
kanta13jp1

Posted on

Indie Dev Analytics — PostHog vs Mixpanel vs Self-Hosted Supabase

Indie Dev Analytics — PostHog vs Mixpanel vs Self-Hosted Supabase

You can't improve what you can't measure. Here's how to pick the right analytics stack for your indie app and implement it in Flutter.

Tool Comparison

Tool Free tier Self-host Indie-friendly
PostHog 1M events/month ◎ (OSS)
Mixpanel 20K MT/month ×
Firebase Analytics Unlimited ×
Self-hosted (Supabase) DB limit only

PostHog Implementation (Recommended)

Open source, self-hostable, GDPR-friendly.

dependencies:
  posthog_flutter: ^4.0.0
Enter fullscreen mode Exit fullscreen mode
await Posthog().setup('YOUR_API_KEY', host: 'https://app.posthog.com');

// Track event
Posthog().capture(
  eventName: 'task_completed',
  properties: {'category': category, 'duration_seconds': duration},
);

// Identify user
Posthog().identify(
  userId: user.id,
  userProperties: {'plan': user.plan, 'email': user.email},
);

// Screen view
Posthog().screen(screenName: 'TaskList');
Enter fullscreen mode Exit fullscreen mode

Self-Hosted Supabase Analytics

Minimum cost, full control. Best under 50K MAU.

CREATE TABLE analytics_events (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  user_id UUID REFERENCES auth.users(id),
  session_id TEXT NOT NULL,
  event_name TEXT NOT NULL,
  properties JSONB DEFAULT '{}',
  platform TEXT,
  app_version TEXT,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX ON analytics_events (event_name, created_at);
CREATE INDEX ON analytics_events (user_id, created_at);
Enter fullscreen mode Exit fullscreen mode
class AnalyticsService {
  final SupabaseClient _client;
  final String _sessionId = const Uuid().v4();

  Future<void> track(String event, {Map<String, dynamic> props = const {}}) async {
    await _client.from('analytics_events').insert({
      'user_id': _client.auth.currentUser?.id,
      'session_id': _sessionId,
      'event_name': event,
      'properties': props,
      'platform': defaultTargetPlatform.name,
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

Auto Screen Tracking with GoRouter

class AnalyticsObserver extends NavigatorObserver {
  @override
  void didPush(Route route, Route? previousRoute) {
    final name = route.settings.name;
    if (name != null) analytics.track('screen_view', props: {'screen': name});
  }
}

final router = GoRouter(observers: [AnalyticsObserver()], routes: [...]);
Enter fullscreen mode Exit fullscreen mode

Events That Matter

class AppEvents {
  // Core value actions
  static const taskCompleted = 'task_completed';
  static const journalWritten = 'journal_written';

  // Engagement signals
  static const streakExtended = 'streak_extended';

  // Monetization funnel
  static const upgradePromptShown = 'upgrade_prompt_shown';
  static const subscriptionStarted = 'subscription_started';
  static const subscriptionCancelled = 'subscription_cancelled';

  // Friction signals (churn precursors)
  static const errorEncountered = 'error_encountered';
  static const featureBlocked = 'feature_blocked';
}
Enter fullscreen mode Exit fullscreen mode

Analysis Queries

-- DAU trend
SELECT DATE_TRUNC('day', created_at) as date, COUNT(DISTINCT user_id) as dau
FROM analytics_events
WHERE created_at >= NOW() - INTERVAL '30 days'
GROUP BY 1 ORDER BY 1;

-- Conversion funnel
SELECT
  COUNT(CASE WHEN event_name = 'upgrade_prompt_shown' THEN 1 END) as shown,
  COUNT(CASE WHEN event_name = 'subscription_started' THEN 1 END) as converted,
  ROUND(100.0 * COUNT(CASE WHEN event_name = 'subscription_started' THEN 1 END) /
    NULLIF(COUNT(CASE WHEN event_name = 'upgrade_prompt_shown' THEN 1 END), 0), 2
  ) as cvr
FROM analytics_events WHERE created_at >= NOW() - INTERVAL '30 days';
Enter fullscreen mode Exit fullscreen mode

My Stack

I use PostHog for product analytics (funnels, cohorts, feature flags) and Supabase for custom ML signals (churn prediction, anomaly detection). The self-hosted data feeds Edge Functions that trigger re-engagement nudges.


What analytics stack are you using for your indie app? I'm curious what conversion rates look like at different scales.

Top comments (0)