api-analytics-setup-posthog
PostHog analytics and feature flags setup
Install
npx skills add https://github.com/agents-inc/skills/tree/main/dist/plugins/api-analytics-setup-posthog/skills/api-analytics-setup-posthog
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install agents-inc-skills@llmmart
git clone https://github.com/agents-inc/skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole agents-inc/skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
PostHog Analytics & Feature Flags Setup
Quick Guide: One-time setup for PostHog analytics and feature flags. Covers
posthog-jsclient provider,posthog-nodeserver client, and environment variables. PostHog handles both analytics AND feature flags with a generous free tier (1M events + 1M flag requests/month).
<critical_requirements>
CRITICAL: Before Using This Skill
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST initialize posthog-js only in a client/browser context - it requires browser APIs like window and localStorage)
(You MUST call posthog.shutdown(), posthog.flush(), or use captureImmediate() after server-side event capture to prevent lost events)
(You MUST use defaults: '2026-01-30' for automatic SPA page tracking and latest recommended behaviors)
</critical_requirements>
Auto-detection: PostHog setup, posthog-js, posthog-node, PostHogProvider, analytics setup, feature flags setup, event tracking setup, posthog.init
When to use:
- Initial PostHog setup in a project
- Configuring PostHogProvider for client-side analytics
- Setting up posthog-node for server-side/API route event capture
- Configuring environment variables for PostHog
When NOT to use:
- Event tracking patterns after setup (use analytics event tracking skill)
- Feature flag usage patterns (use feature flags skill)
- Complex multi-environment setups with separate staging/production projects
Key patterns covered:
- Client-side setup with PostHogProvider or framework initialization hook
- Server-side setup with posthog-node
- Environment variables (client vs server prefix)
- User identification and reset flows
- Serverless flush patterns (captureImmediate vs flush)
Detailed Resources:
- examples/core.md - Provider setup, layout integration, user identification, env vars
- examples/server.md - Server client singleton, API routes, serverless patterns
- reference.md - Decision frameworks
<red_flags>
RED FLAGS
- Initializing posthog-js on the server (requires browser APIs - will crash)
- No
flush()orcaptureImmediate()after server-side capture in serverless environments (events silently lost) - Client-side env vars not exposed to the browser bundle (check your framework's prefix convention)
- Hardcoding API keys in source code instead of environment variables
- Missing
posthog.reset()on sign out (user identity bleeds to next session) - Not using
defaultsdate option (manual pageview tracking required, misses recommended behaviors) - Not calling
posthog.identify()after authentication (anonymous and authenticated sessions remain unlinked) - No
person_profiles: 'identified_only'option (unnecessary anonymous profiles created, higher costs) - Not wrapping app with PostHogProvider when using hooks (hooks return null)
- Forgetting to add environment variables to deployment platform (events fail silently)
- Using different PostHog projects for dev/prod without realizing (separate data)
Gotchas & Edge Cases:
posthog-jsmust be initialized afterwindowis available (hence useEffect or a client-side initialization hook)- Server-side SDK does NOT auto-flush like the client - you must explicitly call
flush(),shutdown(), or usecaptureImmediate() captureImmediate()is simpler for serverless but sends one HTTP request per event (no batching)- Free tier resets monthly (1M events then stops capturing until next month)
person_profiles: 'identified_only'reduces costs but means no anonymous user profiles are created- When using auto-initialization hooks, config values remain fixed for the session - bootstrapping only works if flags are evaluated on the server before render
</red_flags>
<critical_reminders>
CRITICAL REMINDERS
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST initialize posthog-js only in a client/browser context - it requires browser APIs like window and localStorage)
(You MUST call posthog.shutdown(), posthog.flush(), or use captureImmediate() after server-side event capture to prevent lost events)
(You MUST use defaults: '2026-01-30' for automatic SPA page tracking and latest recommended behaviors)
Failure to follow these rules will cause lost analytics events, broken tracking, or security vulnerabilities.
</critical_reminders>
Sources
Files (skills)
-
examples
-
core.md 5.9 KB
# PostHog Setup - Core Examples > Essential patterns for PostHog setup. See [SKILL.md](../SKILL.md) for core concepts and [reference.md](../reference.md) for decision frameworks. > > **Related examples:** [server.md](server.md) --- ## Pattern 1: Client-Side Initialization (Standalone) Initialize posthog-js in a client-side entry point. This is the simplest approach when your framework supports a client initialization hook (e.g., an instrumentation or bootstrap file that runs only in the browser). ```typescript // ✅ Good Example - Client-side init in entry point import posthog from "posthog-js"; const POSTHOG_DEFAULTS_VERSION = "2026-01-30"; posthog.init(process.env.POSTHOG_KEY, { api_host: process.env.POSTHOG_HOST, defaults: POSTHOG_DEFAULTS_VERSION, person_profiles: "identified_only", }); if (process.env.NODE_ENV === "development") { posthog.debug(); } ``` **Why good:** Simplest setup, no provider component needed, `defaults` date enables all recommended behaviors for that snapshot, `person_profiles: "identified_only"` reduces costs **When to use:** When your framework provides a client-side initialization hook. For React apps needing context-based access via hooks, use the PostHogProvider pattern below. --- ## Pattern 2: PostHogProvider Component (React) Create a provider component for React apps that need hook-based access to the PostHog client. ```typescript // ✅ Good Example - PostHog Provider (React) // lib/posthog/provider.tsx "use client"; import { useEffect } from "react"; import posthog from "posthog-js"; import { PostHogProvider as PHProvider } from "posthog-js/react"; const POSTHOG_DEFAULTS_VERSION = "2026-01-30"; interface PostHogProviderProps { children: React.ReactNode; } export function PostHogProvider({ children }: PostHogProviderProps) { useEffect(() => { if (typeof window !== "undefined" && !posthog.__loaded) { posthog.init(process.env.POSTHOG_KEY!, { api_host: process.env.POSTHOG_HOST!, defaults: POSTHOG_DEFAULTS_VERSION, person_profiles: "identified_only", loaded: (posthog) => { if (process.env.NODE_ENV === "development") { posthog.debug(); } }, }); } }, []); return <PHProvider client={posthog}>{children}</PHProvider>; } ``` **Why good:** `defaults` date enables all recommended behaviors for that snapshot, `person_profiles: "identified_only"` reduces costs, `'use client'` ensures browser context, debug mode aids development --- ## Pattern 3: App-Level Provider Integration Wrap the app with PostHogProvider at the root of your component tree. ```typescript // ✅ Good Example - Root layout with PostHog import { PostHogProvider } from "./lib/posthog/provider"; interface RootLayoutProps { children: React.ReactNode; } export function RootLayout({ children }: RootLayoutProps) { return ( <html lang="en"> <body> <PostHogProvider>{children}</PostHogProvider> </body> </html> ); } ``` **Why good:** PostHogProvider wraps entire app, provider handles client-only initialization, children passed through correctly ```typescript // ❌ Bad Example - Initializing posthog-js outside a client context import posthog from "posthog-js"; // BAD: posthog-js requires browser APIs, fails on server posthog.init("phc_xxx", { api_host: "https://us.i.posthog.com" }); export function RootLayout({ children }: { children: React.ReactNode }) { return ( <html lang="en"> <body>{children}</body> </html> ); } ``` **Why bad:** posthog-js uses browser APIs (localStorage, window), initializing in a server-rendered context crashes, no provider means hooks won't work --- ## Pattern 4: User Identification Identify users after authentication to link anonymous and authenticated sessions. ```typescript // ✅ Good Example - Identifying user after sign in import { useEffect } from "react"; import { usePostHog } from "posthog-js/react"; export function usePostHogIdentify( user: { id: string; email: string; name: string } | null, ) { const posthog = usePostHog(); useEffect(() => { if (user) { posthog.identify(user.id, { email: user.email, name: user.name, }); } }, [user, posthog]); } ``` **Why good:** `identify()` links anonymous to authenticated sessions, user properties enable cohort analysis, decoupled from any specific auth library --- ## Pattern 5: Reset on Sign Out Clear user identity when signing out to prevent data bleed. ```typescript // ✅ Good Example - Reset PostHog on sign out import { usePostHog } from "posthog-js/react"; export function useSignOut() { const posthog = usePostHog(); return () => { // After your auth sign-out logic completes: posthog.reset(); }; } ``` **Why good:** `reset()` clears identity on sign out preventing data bleed between users on shared devices ```typescript // ❌ Bad Example - Missing reset on sign out function handleSignOut() { // Sign out logic runs but no posthog.reset() // Next user inherits previous identity! } ``` **Why bad:** User identity bleeds between sessions, corrupts analytics data --- ## Pattern 6: Environment Variables Client-side variables must be exposed to the browser bundle (check your framework's prefix convention). Server-side variables should NOT be exposed. ```bash # ✅ Good Example - .env.local # Client-side (exposed to browser bundle) # Use your framework's public prefix: NEXT_PUBLIC_, VITE_, EXPO_PUBLIC_, etc. POSTHOG_KEY=phc_your_project_api_key POSTHOG_HOST=https://us.i.posthog.com # Server-side only (API routes, never exposed to client) POSTHOG_API_KEY=phc_your_project_api_key ``` **Why good:** Separate client and server env vars, keys never hardcoded in source ```bash # ❌ Bad Example - Hardcoded keys # API keys directly in source code instead of environment variables posthog.init("phc_hardcoded_key_123", { api_host: "..." }); ``` **Why bad:** Hardcoded keys are committed to version control, cannot be rotated, and differ between environments --- -
deployment.md 3.9 KB
# PostHog Setup - Deployment Examples > Environment configuration and deployment patterns for PostHog. See [SKILL.md](../SKILL.md) for core concepts and [reference.md](../reference.md) for decision frameworks. > > **Related examples:** [core.md](core.md) | [server.md](server.md) --- ## Pattern 1: Environment Variables Template Create `.env.example` for team onboarding with clear documentation. ```bash # ✅ Good Example - Comprehensive .env.example # apps/client-next/.env.example # ================================================================ # PostHog Analytics & Feature Flags # ================================================================ # Get your API key from: https://posthog.com -> Project Settings -> API Keys # # Host options: # US Cloud: https://us.i.posthog.com # EU Cloud: https://eu.i.posthog.com # Self-hosted: https://your-posthog-instance.com # Client-side (embedded in browser bundle) NEXT_PUBLIC_POSTHOG_KEY=phc_your_project_api_key NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com # Server-side (API routes, never exposed to client) POSTHOG_API_KEY=phc_your_project_api_key POSTHOG_HOST=https://us.i.posthog.com ``` **Why good:** Comments explain where to get keys, host options documented, clear separation between client and server variables --- ## Pattern 2: Vercel Deployment Configuration Configure PostHog environment variables for production deployment. ### Vercel Environment Variables | Variable | Environment | Value | | -------------------------- | -------------------------------- | -------------------------- | | `NEXT_PUBLIC_POSTHOG_KEY` | Production, Preview, Development | `phc_xxx` | | `NEXT_PUBLIC_POSTHOG_HOST` | Production, Preview, Development | `https://us.i.posthog.com` | | `POSTHOG_API_KEY` | Production, Preview, Development | `phc_xxx` | | `POSTHOG_HOST` | Production, Preview, Development | `https://us.i.posthog.com` | **Why good:** Same project for dev/prod simplifies setup, Vercel handles env var injection at build time ### Local Development Override ```bash # apps/client-next/.env.local (gitignored) # Use the same keys for development NEXT_PUBLIC_POSTHOG_KEY=phc_your_project_api_key NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com POSTHOG_API_KEY=phc_your_project_api_key POSTHOG_HOST=https://us.i.posthog.com ``` **Why good:** `.env.local` is gitignored, allows local overrides, matches production config for consistency --- ## Pattern 3: Initial Dashboard Setup Configure PostHog dashboards after setup completion. ### Recommended Initial Dashboards 1. **Web Analytics Dashboard** (built-in) - Page views, unique visitors, bounce rate - Traffic sources, referrers - Geographic distribution 2. **Product Analytics Dashboard** (create) - Key conversion funnels - Feature adoption rates - Retention cohorts 3. **Error Tracking** (enable plugin) - Error rate trends - Most common errors - Affected users --- ## Pattern 4: Initial Configuration Checklist Use this checklist to verify complete PostHog setup. ```markdown ## PostHog Setup Checklist ### Account Setup - [ ] Created PostHog account and organization - [ ] Created project for app - [ ] Copied API key to .env.local ### Client-Side Setup - [ ] Installed posthog-js (client) - [ ] Created PostHogProvider component - [ ] Wrapped app in provider (layout.tsx) ### Server-Side Setup - [ ] Installed posthog-node (server) - [ ] Created server client singleton - [ ] Added flush() to API routes ### Verification - [ ] Verified events appearing in PostHog dashboard - [ ] Tested in development mode (debug enabled) ### Deployment - [ ] Set up Vercel environment variables - [ ] Created .env.example for team - [ ] Verified events in production ``` **Why good:** Step-by-step verification prevents missed configuration, covers all critical paths, easy to share with team --- -
server.md 3.7 KB
# PostHog Setup - Server-Side Examples > Server-side patterns for PostHog event capture. See [SKILL.md](../SKILL.md) for core concepts and [reference.md](../reference.md) for decision frameworks. > > **Related examples:** [core.md](core.md) --- ## Pattern 1: Server Client Singleton Create a singleton for server-side event capture to prevent multiple client instances. ```typescript // ✅ Good Example - PostHog server client singleton // lib/posthog/server.ts import { PostHog } from "posthog-node"; const FLUSH_INTERVAL_MS = 10000; const FLUSH_AT_COUNT = 20; let posthogClient: PostHog | null = null; export function getPostHogServerClient(): PostHog { if (!posthogClient) { posthogClient = new PostHog(process.env.POSTHOG_API_KEY!, { host: process.env.POSTHOG_HOST || "https://us.i.posthog.com", flushInterval: FLUSH_INTERVAL_MS, flushAt: FLUSH_AT_COUNT, }); } return posthogClient; } // For graceful shutdown in serverless environments export async function shutdownPostHog(): Promise<void> { if (posthogClient) { await posthogClient.shutdown(); posthogClient = null; } } ``` **Why good:** Singleton prevents multiple client instances, flushInterval/flushAt configure batching, shutdown function for graceful cleanup, works in serverless environments --- ## Pattern 2: API Route Usage with flush() Capture events in API routes with proper flush handling for serverless environments. ```typescript // ✅ Good Example - Capturing events in an API route import { getPostHogServerClient } from "./lib/posthog/server"; export async function handleSignup(request: Request) { const posthog = getPostHogServerClient(); const body = await request.json(); // Capture signup event posthog.capture({ distinctId: body.email, event: "user_signed_up", properties: { plan: body.plan, source: body.source, }, }); // CRITICAL: Flush events before response in serverless await posthog.flush(); return Response.json({ success: true }); } ``` **Why good:** `flush()` ensures events are sent before function terminates (important for serverless), distinctId uses user identifier, properties add context --- ## Pattern 2b: Alternative - captureImmediate for Serverless For simpler serverless usage, use `captureImmediate` which awaits the HTTP request directly. ```typescript // ✅ Good Example - Using captureImmediate (recommended for serverless) import { getPostHogServerClient } from "./lib/posthog/server"; export async function handleSignup(request: Request) { const posthog = getPostHogServerClient(); const body = await request.json(); // captureImmediate awaits the HTTP request - no flush needed await posthog.captureImmediate({ distinctId: body.email, event: "user_signed_up", properties: { plan: body.plan, source: body.source, }, }); return Response.json({ success: true }); } ``` **Why good:** `captureImmediate` guarantees the HTTP request finishes before the function continues or shuts down, simpler than managing flush() calls, recommended for serverless by PostHog --- ## Pattern 3: Server-Side Anti-Pattern - Missing Flush ```typescript // ❌ Bad Example - Not flushing events in serverless import { getPostHogServerClient } from "./lib/posthog/server"; export async function handleAction(request: Request) { const posthog = getPostHogServerClient(); posthog.capture({ distinctId: "user-123", event: "action_performed", }); // BAD: No flush() - events may be lost in serverless! return Response.json({ success: true }); } ``` **Why bad:** PostHog batches events by default, serverless function may terminate before batch is sent, events are silently lost. Use `await posthog.flush()` or `captureImmediate()` instead. ---
-
-
reference.md 1.4 KB
# PostHog Setup - Reference Guide > Decision frameworks for PostHog analytics and feature flags setup. See [SKILL.md](SKILL.md) for red flags and gotchas. --- ## Decision Framework ### PostHog Project Structure ``` Single app or tight monorepo? ├─ YES → One PostHog project for all apps │ └─ Use custom properties to filter (app: "web", app: "admin") └─ NO → Multiple distinct products? └─ Separate projects per product └─ Still use ONE organization (pools billing) ``` ### Client vs Server SDK ``` Where is the event triggered? ├─ Browser/React component → posthog-js (usePostHog hook) ├─ API route/server action → posthog-node (getPostHogServerClient) │ └─ Serverless environment? │ ├─ YES → Use captureImmediate() (simplest) │ └─ Or → Use capture() + await flush() ├─ Server-rendered component → posthog-node (but consider if needed) └─ API middleware → posthog-node, flush after response ``` ### US vs EU Hosting ``` Where are your users? ├─ Primarily US/Americas → https://us.i.posthog.com ├─ Primarily EU/GDPR concerns → https://eu.i.posthog.com └─ Self-hosting required → Your own PostHog instance URL ``` --- > **Code examples:** See [examples/core.md](examples/core.md) and [examples/server.md](examples/server.md) for full good/bad comparisons. -
SKILL.md 7.5 KB
--- name: api-analytics-setup-posthog description: PostHog analytics and feature flags setup --- # PostHog Analytics & Feature Flags Setup > **Quick Guide:** One-time setup for PostHog analytics and feature flags. Covers `posthog-js` client provider, `posthog-node` server client, and environment variables. PostHog handles both analytics AND feature flags with a generous free tier (1M events + 1M flag requests/month). --- <critical_requirements> ## CRITICAL: Before Using This Skill > **All code must follow project conventions in CLAUDE.md** (kebab-case, named exports, import ordering, `import type`, named constants) **(You MUST initialize posthog-js only in a client/browser context - it requires browser APIs like window and localStorage)** **(You MUST call `posthog.shutdown()`, `posthog.flush()`, or use `captureImmediate()` after server-side event capture to prevent lost events)** **(You MUST use `defaults: '2026-01-30'` for automatic SPA page tracking and latest recommended behaviors)** </critical_requirements> --- **Auto-detection:** PostHog setup, posthog-js, posthog-node, PostHogProvider, analytics setup, feature flags setup, event tracking setup, posthog.init **When to use:** - Initial PostHog setup in a project - Configuring PostHogProvider for client-side analytics - Setting up posthog-node for server-side/API route event capture - Configuring environment variables for PostHog **When NOT to use:** - Event tracking patterns after setup (use analytics event tracking skill) - Feature flag usage patterns (use feature flags skill) - Complex multi-environment setups with separate staging/production projects **Key patterns covered:** - Client-side setup with PostHogProvider or framework initialization hook - Server-side setup with posthog-node - Environment variables (client vs server prefix) - User identification and reset flows - Serverless flush patterns (captureImmediate vs flush) **Detailed Resources:** - [examples/core.md](examples/core.md) - Provider setup, layout integration, user identification, env vars - [examples/server.md](examples/server.md) - Server client singleton, API routes, serverless patterns - [reference.md](reference.md) - Decision frameworks --- <philosophy> ## Philosophy PostHog is a **product analytics + feature flags platform** that consolidates multiple tools into one. It's open-source, can be self-hosted, and has a generous free tier. For solo developers and small teams, PostHog eliminates the need for separate analytics and feature flag services. **Core principles:** 1. **One platform for analytics + feature flags** - Reduces tool sprawl and cost 2. **Usage-based pricing** - Pay for what you use, not per-project 3. **Autocapture by default** - Automatic event tracking reduces manual instrumentation 4. **Server and client SDKs** - Full coverage for SSR and client-side apps **When to use PostHog:** - Need both analytics and feature flags in one platform - Want generous free tier (1M events + 1M flag requests/month) - Prefer open-source with self-host option - Building product analytics (funnels, retention, sessions) **When NOT to use PostHog:** - Need advanced A/B testing with statistical rigor - Require real-time event streaming - Already have established analytics + flag tools </philosophy> --- <patterns> ## Core Patterns ### Pattern 1: PostHog Project Structure Use a single PostHog organization for your apps. One org pools billing. Use separate projects per app, or one project with custom properties to filter. ``` PostHog Organization: "Your Company" ├── Project: "Main App" (or separate per app) │ ├── API Key: phc_xxx │ └── Host: https://us.i.posthog.com (or eu.i.posthog.com) ``` **Why good:** Single org pools billing across all projects, usage-based pricing, 6 projects included on paid tier --- ### Pattern 2: Client-Side Setup Install `posthog-js` and configure a provider or use your framework's client-side initialization hook. Key config options: `defaults: "2026-01-30"` enables recommended behaviors, `person_profiles: "identified_only"` reduces costs. See [examples/core.md](examples/core.md) for full implementation of both approaches. **Why good:** `defaults` date enables automatic SPA page/leave tracking, `person_profiles: "identified_only"` reduces event costs, debug mode in development aids troubleshooting --- ### Pattern 3: Server-Side Setup with posthog-node Install `posthog-node` and create a singleton for server-side event capture. **Serverless flush options:** - `captureImmediate()` - simplest, awaits HTTP request directly (one request per event) - `capture()` + `await flush()` - batched, requires explicit flush before response returns See [examples/server.md](examples/server.md) for singleton setup, API route usage, and the flush anti-pattern. **Why good:** Singleton prevents multiple client instances, flushInterval/flushAt configure batching, captureImmediate simplifies serverless usage </patterns> --- <red_flags> ## RED FLAGS - Initializing posthog-js on the server (requires browser APIs - will crash) - No `flush()` or `captureImmediate()` after server-side capture in serverless environments (events silently lost) - Client-side env vars not exposed to the browser bundle (check your framework's prefix convention) - Hardcoding API keys in source code instead of environment variables - Missing `posthog.reset()` on sign out (user identity bleeds to next session) - Not using `defaults` date option (manual pageview tracking required, misses recommended behaviors) - Not calling `posthog.identify()` after authentication (anonymous and authenticated sessions remain unlinked) - No `person_profiles: 'identified_only'` option (unnecessary anonymous profiles created, higher costs) - Not wrapping app with PostHogProvider when using hooks (hooks return null) - Forgetting to add environment variables to deployment platform (events fail silently) - Using different PostHog projects for dev/prod without realizing (separate data) **Gotchas & Edge Cases:** - `posthog-js` must be initialized after `window` is available (hence useEffect or a client-side initialization hook) - Server-side SDK does NOT auto-flush like the client - you must explicitly call `flush()`, `shutdown()`, or use `captureImmediate()` - `captureImmediate()` is simpler for serverless but sends one HTTP request per event (no batching) - Free tier resets monthly (1M events then stops capturing until next month) - `person_profiles: 'identified_only'` reduces costs but means no anonymous user profiles are created - When using auto-initialization hooks, config values remain fixed for the session - bootstrapping only works if flags are evaluated on the server before render </red_flags> --- <critical_reminders> ## CRITICAL REMINDERS > **All code must follow project conventions in CLAUDE.md** (kebab-case, named exports, import ordering, `import type`, named constants) **(You MUST initialize posthog-js only in a client/browser context - it requires browser APIs like window and localStorage)** **(You MUST call `posthog.shutdown()`, `posthog.flush()`, or use `captureImmediate()` after server-side event capture to prevent lost events)** **(You MUST use `defaults: '2026-01-30'` for automatic SPA page tracking and latest recommended behaviors)** **Failure to follow these rules will cause lost analytics events, broken tracking, or security vulnerabilities.** </critical_reminders> --- ## Sources - [PostHog JavaScript SDK](https://posthog.com/docs/libraries/js) - [PostHog JavaScript Configuration](https://posthog.com/docs/libraries/js/config) - [PostHog Node.js SDK](https://posthog.com/docs/libraries/node)
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.