api-analytics-posthog-analytics-ff938b — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited api-analytics-posthog-analytics-ff938b (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Quick Guide: Use PostHog for product analytics with structured event naming (category:object_action), 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 callreset()on logout, never store PII in event properties, and usecaptureImmediate()orawait shutdown()in serverless environments.
Detailed Resources:
<critical_requirements>
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering, import type, named constants)(You MUST call `posthog.identify()` ONLY when a user signs up or logs in - never on every page load)
(You MUST include the user's database ID as `distinct_id` in ALL server-side events)
(You MUST call `posthog.reset()` when a user logs out to unlink future events)
(You MUST use the `category:object_action` naming convention for all custom events)
(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:
When NOT to use:
Key patterns covered:
category:object_action)object_adjective, is_/has_ booleans)<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:
</philosophy>
<patterns>
Use the `category:object_action` framework for consistent, queryable event names.
// 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_name, item_countis_ / has_ for booleans: is_first_purchase, has_completed_onboarding_date / _timestamp suffix: trial_end_date, last_login_timestampSee examples/core.md for complete naming examples.
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 userSee examples/core.md for full identification hook and logout handler.
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:
distinctId (user's database ID)captureImmediate() for serverless (guarantees HTTP completion)shutdown() before returning in serverlessflushAt: 1 and flushInterval: 0 for serverlessSee examples/server-tracking.md for complete server setup and route examples.
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.
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 rejectsKey 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>
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:
posthog.init(POSTHOG_KEY, {
person_profiles: "identified_only", // Anonymous events 4x cheaper
autocapture: false, // Disable for high-traffic sites
});</performance>
<red_flags>
High Priority Issues:
distinct_id -- PII should not be the identifierposthog.reset() on logout -- users get mixed togetherawait shutdown() in serverless -- events are lostidentify() on every render -- performance degradationCommon Mistakes:
posthog directly instead of using usePostHog hook in Reactapi_host: "/ingest") -- events blocked by ad blockersperson_profiles: "identified_only" -- 4x higher costs on anonymous eventscapture() instead of captureImmediate() in serverless -- events may not completeGotchas & Edge Cases:
distinct_id is required for ALL server-side events (unlike client-side which auto-generates one)group() must include group ID with every event (not persisted like identify())cookieless_mode: "always" disables identify() entirely -- privacy trade-off</red_flags>
<critical_reminders>
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering, import type, named constants)(You MUST call `posthog.identify()` ONLY when a user signs up or logs in - never on every page load)
(You MUST include the user's database ID as `distinct_id` in ALL server-side events)
(You MUST call `posthog.reset()` when a user logs out to unlink future events)
(You MUST use the `category:object_action` naming convention for all custom events)
(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>
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.