Skills api-analytics-posthog-analytics
PostHog event tracking, user identification, group analytics for B2B, GDPR consent patterns. Use when implementing product analytics, tracking user behavior, setting up funnels, or configuring privacy-compliant tracking.
git clone https://github.com/agents-inc/skills
T=$(mktemp -d) && git clone --depth=1 https://github.com/agents-inc/skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/src/skills/api-analytics-posthog-analytics" ~/.claude/skills/agents-inc-skills-api-analytics-posthog-analytics-a1d246 && rm -rf "$T"
src/skills/api-analytics-posthog-analytics/SKILL.mdPostHog Analytics Patterns
Quick Guide: Use PostHog for product analytics with structured event naming (
), server-side tracking for reliability, and proper user identification integrated with your authentication flow. Client-side for UI interactions, server-side for business events. Always callcategory:object_actionon logout, never store PII in event properties, and usereset()orcaptureImmediate()in serverless environments.await shutdown()
Detailed Resources:
- examples/core.md - Event naming, user identification, property conventions
- examples/client-tracking.md - React hooks, provider setup, component tracking
- examples/server-tracking.md - posthog-node, serverless patterns, auth events
- examples/group-analytics.md - B2B organization tracking
- examples/privacy-gdpr.md - GDPR consent, cookieless mode, PII filtering
- reference.md - Decision frameworks, anti-patterns, event taxonomy
<critical_requirements>
CRITICAL: Before Using This Skill
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
, named constants)import type
(You MUST call
ONLY when a user signs up or logs in - never on every page load)posthog.identify()
(You MUST include the user's database ID as
in ALL server-side events)distinct_id
(You MUST call
when a user logs out to unlink future events)posthog.reset()
(You MUST use the
naming convention for all custom events)category:object_action
(You MUST NEVER include PII (email, name, phone) in event properties - use user IDs only)
</critical_requirements>
Auto-detection: PostHog, posthog-js, posthog-node, usePostHog, PostHogProvider, capture, identify, group analytics, product analytics, event tracking, funnel analysis
When to use:
- Tracking user behavior and product analytics
- Setting up conversion funnels and retention analysis
- Implementing group analytics for B2B multi-tenant apps
- Understanding feature adoption and user journeys
- A/B testing analysis (in conjunction with feature flags)
When NOT to use:
- Feature flag implementation (separate concern)
- Error tracking and logging (use dedicated error tracking tools)
- Infrastructure monitoring (use observability tools)
Key patterns covered:
- Event naming conventions (
)category:object_action - Property naming patterns (
,object_adjective
/is_
booleans)has_ - User identification with authentication flow integration
- Client-side tracking with React hooks
- Server-side tracking with posthog-node
- Group analytics for B2B organizations
- Privacy and GDPR consent patterns
- TypeScript patterns for type-safe events
<philosophy>
Philosophy
PostHog analytics follows a structured taxonomy approach: consistent naming conventions, meaningful properties, and strategic placement (client vs server). Track what matters for product decisions, not everything.
Core principles:
- Server-side for business events - User signups, purchases, subscriptions (reliable, not blocked)
- Client-side for UI interactions - Button clicks, page views, form interactions
- Identify once per session - Not on every page load
- Structured naming - Makes querying and analysis possible at scale
<patterns>
Core Patterns
Pattern 1: Event Naming Conventions
Use the
framework for consistent, queryable event names.category:object_action
// category: Context (signup_flow, settings, dashboard) // object: Component/location (password_button, pricing_page) // action: Present-tense verb (click, submit, view) "signup_flow:email_form_submit"; "dashboard:project_create"; "settings:billing_plan_upgrade"; // Simpler alternative: object_verb "project_created"; "user_signed_up";
Why good: Category prefix groups related events in PostHog UI, enables wildcard queries like
signup_flow:*, consistent naming makes analysis possible at scale.
Property naming rules:
:object_adjective
,project_id
,plan_nameitem_count
/is_
for booleans:has_
,is_first_purchasehas_completed_onboarding
/_date
suffix:_timestamp
,trial_end_datelast_login_timestamp
See examples/core.md for complete naming examples.
Pattern 2: User Identification with Authentication
Call
identify() only on auth state change (not every render). Use database user ID as distinct_id. Call reset() on logout.
// Check _isIdentified() to prevent duplicate calls useEffect(() => { if (session?.user && !posthog._isIdentified()) { posthog.identify(session.user.id, { plan: session.user.plan ?? "free", created_at: session.user.createdAt, is_verified: session.user.emailVerified ?? false, }); } }, [session?.user]);
// Always reset on logout posthog?.capture("user_logged_out"); posthog?.reset(); // Unlink future events from this user
See examples/core.md for full identification hook and logout handler.
Pattern 3: Server-Side Tracking
Track business events reliably from your backend with posthog-node.
// Serverless: use captureImmediate (guarantees HTTP completion) await posthogServer.captureImmediate({ distinctId: user.id, event: "subscription_created", properties: { plan: "pro", is_annual: true }, }); // Always call shutdown before returning in serverless await posthogServer.shutdown();
Key rules:
- Always include
(user's database ID)distinctId - Use
for serverless (guarantees HTTP completion)captureImmediate() - Always call
before returning in serverlessshutdown() - Configure
andflushAt: 1
for serverlessflushInterval: 0
See examples/server-tracking.md for complete server setup and route examples.
Pattern 4: Group Analytics (B2B)
Associate events with organizations using PostHog groups for B2B metrics.
// Client-side: identify organization posthog.group("company", org.id, { name: org.name, plan: org.plan ?? "free", member_count: org.memberCount, }); // Server-side: include groups in event posthogServer.capture({ distinctId: user.id, event: "organization:member_invited", properties: { role: data.role }, groups: { company: data.organizationId }, });
Limitations: Maximum 5 group types per project. One group per type per event.
See examples/group-analytics.md for complete group patterns.
Pattern 5: Privacy and GDPR Consent
PostHog supports cookieless tracking and consent management.
// Cookieless mode: "always" (no consent needed) or "on_reject" (with banner) posthog.init(POSTHOG_KEY, { cookieless_mode: "on_reject", person_profiles: "identified_only", }); // Consent methods posthog.opt_in_capturing(); // User accepts posthog.opt_out_capturing(); // User rejects
Key rule: Never store PII (email, name, phone, IP, address) in event properties. Use pseudonymized IDs only.
See examples/privacy-gdpr.md for consent banner integration and
before_send filtering.
</patterns>
<performance>
Performance Optimization
Web Apps (default batching): Use default settings -- PostHog batches efficiently out of the box.
Serverless (immediate delivery):
const posthogServer = new PostHog(POSTHOG_KEY, { flushAt: 1, // Flush after 1 event flushInterval: 0, // No interval batching }); // Use captureImmediate() or capture() + await shutdown()
Reducing Costs:
</performance>posthog.init(POSTHOG_KEY, { person_profiles: "identified_only", // Anonymous events 4x cheaper autocapture: false, // Disable for high-traffic sites });
<red_flags>
RED FLAGS
High Priority Issues:
- Using email as
-- PII should not be the identifierdistinct_id - Missing
on logout -- users get mixed togetherposthog.reset() - No
in serverless -- events are lostawait shutdown() - PII in event properties -- GDPR violation risk
- Calling
on every render -- performance degradationidentify()
Common Mistakes:
- Importing
directly instead of usingposthog
hook in ReactusePostHog - Not setting up reverse proxy (
) -- events blocked by ad blockersapi_host: "/ingest" - Different event names for same action on frontend vs backend
- Not using
-- 4x higher costs on anonymous eventsperson_profiles: "identified_only" - Using
instead ofcapture()
in serverless -- events may not completecaptureImmediate()
Gotchas & Edge Cases:
is required for ALL server-side events (unlike client-side which auto-generates one)distinct_id
must include group ID with every event (not persisted likegroup()
)identify()- Maximum 5 group types per project
disablescookieless_mode: "always"
entirely -- privacy trade-offidentify()- PostHog web SDK is client-side only -- will not work in server components
- Session IDs must be manually passed to server-side events for session linking
</red_flags>
<critical_reminders>
CRITICAL REMINDERS
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
, named constants)import type
(You MUST call
ONLY when a user signs up or logs in - never on every page load)posthog.identify()
(You MUST include the user's database ID as
in ALL server-side events)distinct_id
(You MUST call
when a user logs out to unlink future events)posthog.reset()
(You MUST use the
naming convention for all custom events)category:object_action
(You MUST NEVER include PII (email, name, phone) in event properties - use user IDs only)
Failure to follow these rules will cause analytics data quality issues, privacy violations, or lost events.
</critical_reminders>