posthog-analytics

PostHog analytics, event tracking, feature flags, dashboards

INSTALLATION
npx skills add https://github.com/alinaqi/claude-bootstrap --skill posthog-analytics
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

PostHog Analytics Skill

For implementing product analytics with PostHog - event tracking, user identification, feature flags, and project-specific dashboards.

Sources: PostHog Docs | Product Analytics | Feature Flags

Philosophy

Measure what matters, not everything.

Analytics should answer specific questions:

  • Are users getting value? (activation, retention)
  • Where do users struggle? (funnels, drop-offs)
  • What features drive engagement? (feature usage)
  • Is the product growing? (acquisition, referrals)

Don't track everything. Track what informs decisions.

Installation

Next.js (App Router)

npm install posthog-js
// lib/posthog.ts

import posthog from 'posthog-js';

export function initPostHog() {

  if (typeof window !== 'undefined' && !posthog.__loaded) {

    posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {

      api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST || 'https://us.i.posthog.com',

      person_profiles: 'identified_only', // Only create profiles for identified users

      capture_pageview: false, // We'll handle this manually for SPA

      capture_pageleave: true,

      loaded: (posthog) => {

        if (process.env.NODE_ENV === 'development') {

          posthog.debug();

        }

      },

    });

  }

  return posthog;

}

export { posthog };
// app/providers.tsx

'use client';

import { useEffect } from 'react';

import { usePathname, useSearchParams } from 'next/navigation';

import { initPostHog, posthog } from '@/lib/posthog';

export function PostHogProvider({ children }: { children: React.ReactNode }) {

  const pathname = usePathname();

  const searchParams = useSearchParams();

  useEffect(() => {

    initPostHog();

  }, []);

  // Track pageviews

  useEffect(() => {

    if (pathname) {

      let url = window.origin + pathname;

      if (searchParams.toString()) {

        url += `?${searchParams.toString()}`;

      }

      posthog.capture('$pageview', { $current_url: url });

    }

  }, [pathname, searchParams]);

  return <>{children}</>;

}
// app/layout.tsx

import { PostHogProvider } from './providers';

export default function RootLayout({ children }: { children: React.ReactNode }) {

  return (

    <html lang="en">

      <body>

        <PostHogProvider>

          {children}

        </PostHogProvider>

      </body>

    </html>

  );

}

React (Vite/CRA)

// src/posthog.ts

import posthog from 'posthog-js';

posthog.init(import.meta.env.VITE_POSTHOG_KEY, {

  api_host: import.meta.env.VITE_POSTHOG_HOST || 'https://us.i.posthog.com',

  person_profiles: 'identified_only',

});

export { posthog };
// src/main.tsx

import { PostHogProvider } from 'posthog-js/react';

import { posthog } from './posthog';

ReactDOM.createRoot(document.getElementById('root')!).render(

  <PostHogProvider client={posthog}>

    <App />

  </PostHogProvider>

);

Python (FastAPI/Flask)

pip install posthog
# analytics/posthog_client.py

import posthog

from functools import lru_cache

@lru_cache()

def get_posthog():

    posthog.project_api_key = os.environ["POSTHOG_API_KEY"]

    posthog.host = os.environ.get("POSTHOG_HOST", "https://us.i.posthog.com")

    posthog.debug = os.environ.get("ENV") == "development"

    return posthog

# Usage

def track_event(user_id: str, event: str, properties: dict = None):

    ph = get_posthog()

    ph.capture(

        distinct_id=user_id,

        event=event,

        properties=properties or {}

    )

def identify_user(user_id: str, properties: dict):

    ph = get_posthog()

    ph.identify(user_id, properties)

Node.js (Express/Hono)

npm install posthog-node
// lib/posthog.ts

import { PostHog } from 'posthog-node';

const posthog = new PostHog(process.env.POSTHOG_API_KEY!, {

  host: process.env.POSTHOG_HOST || 'https://us.i.posthog.com',

});

// Flush on shutdown

process.on('SIGTERM', () => posthog.shutdown());

export { posthog };

// Usage

export function trackEvent(userId: string, event: string, properties?: Record<string, any>) {

  posthog.capture({

    distinctId: userId,

    event,

    properties,

  });

}

export function identifyUser(userId: string, properties: Record<string, any>) {

  posthog.identify({

    distinctId: userId,

    properties,

  });

}

Environment Variables

# .env.local (Next.js) - SAFE: These are meant to be public

NEXT_PUBLIC_POSTHOG_KEY=phc_xxxxxxxxxxxxxxxxxxxx

NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com

# .env (Backend) - Keep private

POSTHOG_API_KEY=phc_xxxxxxxxxxxxxxxxxxxx

POSTHOG_HOST=https://us.i.posthog.com

Add to credentials.md patterns:

'POSTHOG_API_KEY': r'phc_[A-Za-z0-9]+',

User Identification

When to Identify

// Identify on signup

async function handleSignup(email: string, name: string) {

  const user = await createUser(email, name);

  posthog.identify(user.id, {

    email: user.email,

    name: user.name,

    created_at: user.createdAt,

    plan: 'free',

  });

  posthog.capture('user_signed_up', {

    signup_method: 'email',

  });

}

// Identify on login

async function handleLogin(email: string) {

  const user = await authenticateUser(email);

  posthog.identify(user.id, {

    email: user.email,

    name: user.name,

    plan: user.plan,

    last_login: new Date().toISOString(),

  });

  posthog.capture('user_logged_in');

}

// Reset on logout

function handleLogout() {

  posthog.capture('user_logged_out');

  posthog.reset(); // Clears identity

}

User Properties

// Standard properties to track

interface UserProperties {

  // Identity

  email: string;

  name: string;

  // Lifecycle

  created_at: string;

  plan: 'free' | 'pro' | 'enterprise';

  // Engagement

  onboarding_completed: boolean;

  feature_count: number;

  // Business

  company_name?: string;

  company_size?: string;

  industry?: string;

}

// Update properties when they change

posthog.capture('$set', {

  $set: { plan: 'pro' },

});

Event Tracking Patterns

Event Naming Convention

// Format: [object]_[action]

// Use snake_case, past tense for actions

// ✅ Good event names

'user_signed_up'

'feature_created'

'subscription_upgraded'

'onboarding_completed'

'invite_sent'

'file_uploaded'

'search_performed'

'checkout_started'

'payment_completed'

// ❌ Bad event names

'click'           // Too vague

'ButtonClick'     // Not snake_case

'user signup'     // Spaces

'creatingFeature' // Not past tense

Core Events by Category

// === AUTHENTICATION ===

posthog.capture('user_signed_up', {

  signup_method: 'google' | 'email' | 'github',

  referral_source: 'organic' | 'paid' | 'referral',

});

posthog.capture('user_logged_in', {

  login_method: 'google' | 'email' | 'magic_link',

});

posthog.capture('user_logged_out');

posthog.capture('password_reset_requested');

// === ONBOARDING ===

posthog.capture('onboarding_started');

posthog.capture('onboarding_step_completed', {

  step_name: 'profile' | 'preferences' | 'first_action',

  step_number: 1,

  total_steps: 3,

});

posthog.capture('onboarding_completed', {

  duration_seconds: 120,

  steps_skipped: 0,

});

posthog.capture('onboarding_skipped', {

  skipped_at_step: 2,

});

// === FEATURE USAGE ===

posthog.capture('feature_used', {

  feature_name: 'export' | 'share' | 'duplicate',

  context: 'dashboard' | 'editor',

});

posthog.capture('[resource]_created', {

  resource_type: 'project' | 'document' | 'team',

  // Resource-specific properties

});

posthog.capture('[resource]_updated', {

  resource_type: 'project',

  fields_changed: ['name', 'description'],

});

posthog.capture('[resource]_deleted', {

  resource_type: 'project',

});

// === BILLING ===

posthog.capture('pricing_page_viewed', {

  current_plan: 'free',

});

posthog.capture('checkout_started', {

  plan: 'pro',

  billing_period: 'monthly' | 'annual',

  price: 29,

});

posthog.capture('subscription_upgraded', {

  from_plan: 'free',

  to_plan: 'pro',

  mrr_change: 29,

});

posthog.capture('subscription_downgraded', {

  from_plan: 'pro',

  to_plan: 'free',

  reason: 'too_expensive' | 'missing_features' | 'not_using',

});

posthog.capture('subscription_cancelled', {

  plan: 'pro',

  reason: 'string',

  feedback: 'string',

});

// === ERRORS ===

posthog.capture('error_occurred', {

  error_type: 'api_error' | 'validation_error' | 'network_error',

  error_message: 'string',

  error_code: 'string',

  page: '/dashboard',

});

React Hook for Tracking

// hooks/useTrack.ts

import { useCallback } from 'react';

import { posthog } from '@/lib/posthog';

export function useTrack() {

  const track = useCallback((event: string, properties?: Record<string, any>) => {

    posthog.capture(event, {

      ...properties,

      timestamp: new Date().toISOString(),

    });

  }, []);

  return { track };

}

// Usage

function CreateProjectButton() {

  const { track } = useTrack();

  const handleCreate = async () => {

    track('project_creation_started');

    try {

      const project = await createProject();

      track('project_created', {

        project_id: project.id,

        template_used: project.template,

      });

    } catch (error) {

      track('project_creation_failed', {

        error_message: error.message,

      });

    }

  };

  return <button onClick={handleCreate}>Create Project</button>;

}

Feature Flags

Setup

// Check feature flag (client-side)

import { useFeatureFlagEnabled } from 'posthog-js/react';

function NewFeature() {

  const showNewUI = useFeatureFlagEnabled('new-dashboard-ui');

  if (showNewUI) {

    return <NewDashboard />;

  }

  return <OldDashboard />;

}

// With payload

import { useFeatureFlagPayload } from 'posthog-js/react';

function PricingPage() {

  const pricingConfig = useFeatureFlagPayload('pricing-experiment');

  // pricingConfig = { price: 29, showAnnual: true }

  return <Pricing config={pricingConfig} />;

}

Server-Side (Next.js)

// app/dashboard/page.tsx

import { PostHog } from 'posthog-node';

import { cookies } from 'next/headers';

async function getFeatureFlags(userId: string) {

  const posthog = new PostHog(process.env.POSTHOG_API_KEY!);

  const flags = await posthog.getAllFlags(userId);

  await posthog.shutdown();

  return flags;

}

export default async function Dashboard() {

  const cookieStore = cookies();

  const userId = cookieStore.get('user_id')?.value;

  const flags = await getFeatureFlags(userId);

  return (

    <div>

      {flags['new-dashboard'] && <NewFeature />}

    </div>

  );

}

A/B Testing

// Track experiment exposure

function ExperimentComponent() {

  const variant = useFeatureFlagEnabled('checkout-experiment');

  useEffect(() => {

    posthog.capture('experiment_viewed', {

      experiment: 'checkout-experiment',

      variant: variant ? 'test' : 'control',

    });

  }, [variant]);

  return variant ? <NewCheckout /> : <OldCheckout />;

}

Project-Specific Dashboards

SaaS Product

## Essential SaaS Dashboards

### 1. Acquisition Dashboard

**Questions answered:** Where do users come from? What converts?

Insights to create:

- [ ] Signups by source (daily/weekly trend)

- [ ] Signup conversion rate by landing page

- [ ] Time from first visit to signup

- [ ] Signup funnel: Visit → Signup Page → Form Start → Complete

### 2. Activation Dashboard

**Questions answered:** Are new users getting value?

Insights to create:

- [ ] Onboarding completion rate

- [ ] Time to first key action

- [ ] Activation rate (% reaching "aha moment" in first 7 days)

- [ ] Drop-off by onboarding step

- [ ] Feature adoption in first session

### 3. Engagement Dashboard

**Questions answered:** How are users using the product?

Insights to create:

- [ ] DAU/WAU/MAU trends

- [ ] Feature usage heatmap

- [ ] Session duration distribution

- [ ] Actions per session

- [ ] Power users vs casual users

### 4. Retention Dashboard

**Questions answered:** Are users coming back?

Insights to create:

- [ ] Retention cohorts (D1, D7, D30)

- [ ] Churn rate by plan

- [ ] Reactivation rate

- [ ] Last action before churn

- [ ] Features correlated with retention

### 5. Revenue Dashboard

**Questions answered:** Is the business growing?

Insights to create:

- [ ] MRR trend

- [ ] Upgrades vs downgrades

- [ ] Trial to paid conversion

- [ ] Revenue by plan

- [ ] LTV by acquisition source

E-Commerce

## Essential E-Commerce Dashboards

### 1. Conversion Funnel

Insights to create:

- [ ] Full funnel: Browse → PDP → Add to Cart → Checkout → Purchase

- [ ] Cart abandonment rate

- [ ] Checkout drop-off by step

- [ ] Payment failure rate

### 2. Product Performance

Insights to create:

- [ ] Product views → purchases (by product)

- [ ] Add to cart rate by category

- [ ] Search → purchase correlation

- [ ] Cross-sell effectiveness

### 3. Customer Dashboard

Insights to create:

- [ ] Repeat purchase rate

- [ ] Average order value trend

- [ ] Customer lifetime value

- [ ] Purchase frequency distribution

Content/Media

## Essential Content Dashboards

### 1. Consumption Dashboard

Insights to create:

- [ ] Content views by type

- [ ] Read/watch completion rate

- [ ] Time on content

- [ ] Scroll depth distribution

### 2. Engagement Dashboard

Insights to create:

- [ ] Shares by content

- [ ] Comments per article

- [ ] Save/bookmark rate

- [ ] Return visits to same content

### 3. Growth Dashboard

Insights to create:

- [ ] New vs returning visitors

- [ ] Email signup rate

- [ ] Referral traffic sources

AI/LLM Application

## Essential AI App Dashboards

### 1. Usage Dashboard

Insights to create:

- [ ] Queries per user per day

- [ ] Token usage distribution

- [ ] Response time p50/p95

- [ ] Error rate by query type

### 2. Quality Dashboard

Insights to create:

- [ ] User feedback (thumbs up/down)

- [ ] Regeneration rate (user asked for new response)

- [ ] Edit rate (user modified AI output)

- [ ] Follow-up query rate

### 3. Cost Dashboard

Insights to create:

- [ ] Token cost per user

- [ ] Cost by model

- [ ] Cost by feature

- [ ] Efficiency trends (value/cost)

Creating Dashboards

Using PostHog MCP

When setting up analytics for a project:

1. First, check existing dashboards:

   - Use `dashboards-get-all` to list current dashboards

2. Create project-appropriate dashboards:

   - Use `dashboard-create` with descriptive name

3. Create insights for each dashboard:

   - Use `query-run` to test queries

   - Use `insight-create-from-query` to save

   - Use `add-insight-to-dashboard` to organize

4. Set up key funnels:

   - Signup funnel

   - Onboarding funnel

   - Purchase/conversion funnel

Dashboard Creation Workflow

// Example: Creating SaaS dashboards via MCP

// 1. Create dashboard

const dashboard = await mcp_posthog_dashboard_create({

  name: "Activation Metrics",

  description: "Track new user activation and onboarding",

  tags: ["saas", "activation"],

});

// 2. Create insights

const signupFunnel = await mcp_posthog_query_run({

  query: {

    kind: "InsightVizNode",

    source: {

      kind: "FunnelsQuery",

      series: [

        { kind: "EventsNode", event: "user_signed_up", name: "Signed Up" },

        { kind: "EventsNode", event: "onboarding_started", name: "Started Onboarding" },

        { kind: "EventsNode", event: "onboarding_completed", name: "Completed Onboarding" },

        { kind: "EventsNode", event: "first_value_action", name: "First Value" },

      ],

      dateRange: { date_from: "-30d" },

    },

  },

});

// 3. Save and add to dashboard

const insight = await mcp_posthog_insight_create_from_query({

  name: "Signup to Activation Funnel",

  query: signupFunnel.query,

  favorited: true,

});

await mcp_posthog_add_insight_to_dashboard({

  insightId: insight.id,

  dashboardId: dashboard.id,

});

Privacy & Compliance

GDPR Compliance

// Opt-out handling

export function handleCookieConsent(consent: boolean) {

  if (consent) {

    posthog.opt_in_capturing();

  } else {

    posthog.opt_out_capturing();

  }

}

// Check consent status

const hasConsent = posthog.has_opted_in_capturing();

// Initialize with consent check

posthog.init(key, {

  opt_out_capturing_by_default: true, // Require explicit opt-in

  respect_dnt: true, // Respect Do Not Track

});

Data to Never Track

// ❌ NEVER track these

posthog.capture('event', {

  password: '...',           // Credentials

  credit_card: '...',        // Payment info

  ssn: '...',                // Government IDs

  medical_info: '...',       // Health data

  full_address: '...',       // Detailed location

});

// ✅ OK to track

posthog.capture('event', {

  country: 'US',             // General location

  plan: 'pro',               // Product info

  feature_used: 'export',    // Usage

});

Property Sanitization

// lib/analytics.ts

const SENSITIVE_KEYS = ['password', 'token', 'secret', 'credit', 'ssn'];

function sanitizeProperties(props: Record<string, any>): Record<string, any> {

  return Object.fromEntries(

    Object.entries(props).filter(([key]) =>

      !SENSITIVE_KEYS.some(sensitive => key.toLowerCase().includes(sensitive))

    )

  );

}

export function safeCapture(event: string, properties?: Record<string, any>) {

  posthog.capture(event, sanitizeProperties(properties || {}));

}

Testing Analytics

Development Mode

// Disable in development

if (process.env.NODE_ENV === 'development') {

  posthog.opt_out_capturing();

  // Or use debug mode

  posthog.debug();

}

E2E Testing

// playwright/fixtures.ts

import { test as base } from '@playwright/test';

export const test = base.extend({

  page: async ({ page }, use) => {

    // Mock PostHog to capture events

    await page.addInitScript(() => {

      window.capturedEvents = [];

      window.posthog = {

        capture: (event, props) => {

          window.capturedEvents.push({ event, props });

        },

        identify: () => {},

        reset: () => {},

      };

    });

    await use(page);

  },

});

// In tests

test('tracks signup event', async ({ page }) => {

  await page.goto('/signup');

  await page.fill('[name=email]', 'test@example.com');

  await page.click('button[type=submit]');

  const events = await page.evaluate(() => window.capturedEvents);

  expect(events).toContainEqual({

    event: 'user_signed_up',

    props: expect.objectContaining({ signup_method: 'email' }),

  });

});

Debugging

PostHog Toolbar

// Enable toolbar for debugging

posthog.init(key, {

  // ...

  loaded: (posthog) => {

    if (process.env.NODE_ENV === 'development') {

      posthog.debug();

      // Toolbar available via PostHog dashboard

    }

  },

});

Event Debugging

// Log all events in development

posthog.init(key, {

  _onCapture: (eventName, eventData) => {

    if (process.env.NODE_ENV === 'development') {

      console.log('PostHog Event:', eventName, eventData);

    }

  },

});

Quick Reference

Event Checklist by User Lifecycle

## Must-Track Events

### Acquisition

- [ ] `page_viewed` (automatic with capture_pageview)

- [ ] `user_signed_up`

- [ ] `user_logged_in`

### Activation

- [ ] `onboarding_started`

- [ ] `onboarding_step_completed`

- [ ] `onboarding_completed`

- [ ] `first_[key_action]` (your "aha moment")

### Engagement

- [ ] `[feature]_used`

- [ ] `[resource]_created`

- [ ] `search_performed`

- [ ] `invite_sent`

### Revenue

- [ ] `pricing_page_viewed`

- [ ] `checkout_started`

- [ ] `subscription_upgraded`

- [ ] `subscription_cancelled`

### Retention

- [ ] `session_started`

- [ ] `feature_[x]_used` (power features)

Dashboard Templates

Project TypeKey Dashboards
SaaSAcquisition, Activation, Engagement, Retention, Revenue
E-CommerceConversion Funnel, Product Performance, Customer LTV
ContentConsumption, Engagement, Growth
AI/LLMUsage, Quality, Cost
Mobile AppInstalls, Onboarding, DAU/MAU, Crashes

Properties to Always Include

// Auto-enriched by PostHog

$current_url

$browser

$device_type

$os

// Add these yourself

user_plan       // 'free' | 'pro' | 'enterprise'

user_role       // 'admin' | 'member'

company_id      // For B2B

feature_context // Where in the app
BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card