Claude Code Plugins

Community-maintained marketplace

Feedback

posthog-analytics

@alinaqi/claude-bootstrap
320
0

PostHog analytics, event tracking, feature flags, dashboards

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name posthog-analytics
description PostHog analytics, event tracking, feature flags, dashboards

PostHog Analytics Skill

Load with: base.md + [framework].md

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 Type Key Dashboards
SaaS Acquisition, Activation, Engagement, Retention, Revenue
E-Commerce Conversion Funnel, Product Performance, Customer LTV
Content Consumption, Engagement, Growth
AI/LLM Usage, Quality, Cost
Mobile App Installs, 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