Install any skill in seconds. Free to start, no credit card required.
Get Started Free →PostHog analytics, event tracking, feature flags, dashboards
.claude/skills/nicepkg-posthog-analytics/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 151% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 226% | 0% |
| case-13 | ✗→✓ | ▲ Improved | 198% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 172% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 242% | 0% |
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
Measure what matters, not everything.
Analytics should answer specific questions:
Don't track everything. Track what informs decisions.
bashnpm install posthog-js
typescript// 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 };
typescript// 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}</>; }
typescript// 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> ); }
typescript// 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 };
typescript// src/main.tsx import { PostHogProvider } from 'posthog-js/react'; import { posthog } from './posthog'; ReactDOM.createRoot(document.getElementById('root')!).render( <PostHogProvider client={posthog}> <App /> </PostHogProvider> );
bashpip install posthog
python# 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)
bashnpm install posthog-node
typescript// 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, }); }
bash# .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:
python'POSTHOG_API_KEY': r'phc_[A-Za-z0-9]+',
typescript// 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 }
typescript// 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' }, });
typescript// 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
typescript// === 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', });
typescript// 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>; }
typescript// 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} />; }
typescript// 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> ); }
typescript// 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 />; }
markdown## 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
markdown## 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
markdown## 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
markdown## 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)
markdownWhen 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
typescript// 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, });
typescript// 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 });
typescript// ❌ 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 });
typescript// 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 || {})); }
typescript// Disable in development if (process.env.NODE_ENV === 'development') { posthog.opt_out_capturing(); // Or use debug mode posthog.debug(); }
typescript// 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' }), }); });
typescript// Enable toolbar for debugging posthog.init(key, { // ... loaded: (posthog) => { if (process.env.NODE_ENV === 'development') { posthog.debug(); // Toolbar available via PostHog dashboard } }, });
typescript// Log all events in development posthog.init(key, { _onCapture: (eventName, eventData) => { if (process.env.NODE_ENV === 'development') { console.log('PostHog Event:', eventName, eventData); } }, });
markdown## 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)
| 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 |
typescript// 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
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-03 | pass→pass | 15,615 | 9,279 | -41% | 1 | 1 | 0% | 2,753 | 7,868 | +186% | 0 | 0 | — |
case-01 | fail→pass | 19,353 | 14,971 | -23% | 1 | 1 | 0% | 3,689 | 9,251 | +151% | 0 | 0 | — |
case-02 | fail→fail | 18,954 | 17,777 | -6% | 1 | 1 | 0% | 3,438 | 9,737 | +183% | 0 | 0 | — |
case-04 | pass→pass | 17,316 | 13,135 | -24% | 1 | 1 | 0% | 3,232 | 8,752 | +171% | 0 | 0 | — |
case-05 | pass→pass | 8,856 | 7,302 | -18% | 1 | 1 | 0% | 1,467 | 7,558 | +415% | 0 | 0 | — |
case-06 | fail→pass | 15,204 | 14,332 | -6% | 1 | 1 | 0% | 2,672 | 8,706 | +226% | 0 | 0 | — |
case-07 | pass→pass | 19,072 | 18,086 | -5% | 1 | 1 | 0% | 3,591 | 9,771 | +172% | 0 | 0 | — |
case-13 | fail→pass | 15,414 | 18,525 | +20% | 1 | 1 | 0% | 3,296 | 9,815 | +198% | 0 | 0 | — |
case-08 | pass→pass | 12,589 | 8,076 | -36% | 1 | 1 | 0% | 2,075 | 7,740 | +273% | 0 | 0 | — |
case-09 | pass→pass | 10,472 | 5,735 | -45% | 1 | 1 | 0% | 1,827 | 7,064 | +287% | 0 | 0 | — |
case-10 | pass→pass | 14,796 | 15,210 | +3% | 1 | 1 | 0% | 2,529 | 8,893 | +252% | 0 | 0 | — |
case-11 | fail→pass | 16,179 | 10,196 | -37% | 1 | 1 | 0% | 3,007 | 8,188 | +172% | 0 | 0 | — |
case-12 | fail→pass | 13,628 | 12,170 | -11% | 1 | 1 | 0% | 2,512 | 8,587 | +242% | 0 | 0 | — |
case-14 | pass→pass | 19,574 | 4,565 | -77% | 1 | 1 | 0% | 1,713 | 6,947 | +306% | 0 | 0 | — |
case-15 | fail→fail | 12,961 | 12,186 | -6% | 1 | 1 | 0% | 2,131 | 8,205 | +285% | 0 | 0 | — |
case-16 | fail→pass | 13,466 | 12,804 | -5% | 1 | 1 | 0% | 2,098 | 8,446 | +303% | 0 | 0 | — |
case-17 | fail→pass | 12,325 | 9,220 | -25% | 1 | 1 | 0% | 2,135 | 7,919 | +271% | 0 | 0 | — |
case-18 | fail→pass | 9,546 | 6,544 | -31% | 1 | 1 | 0% | 1,584 | 7,289 | +360% | 0 | 0 | — |
case-19 | pass→pass | 15,215 | 13,298 | -13% | 1 | 1 | 0% | 2,877 | 8,846 | +207% | 0 | 0 | — |
case-20 | pass→pass | 14,146 | 13,879 | -2% | 1 | 1 | 0% | 2,898 | 9,067 | +213% | 0 | 0 | — |
case-21 | pass→pass | 13,058 | 8,989 | -31% | 1 | 1 | 0% | 2,444 | 7,858 | +222% | 0 | 0 | — |
case-22 | pass→pass | 12,929 | 11,986 | -7% | 1 | 1 | 0% | 2,521 | 8,579 | +240% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +36 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.