PostHog Analytics skill for Codex

One person per user, flags without flicker. Codex sets up PostHog with a pinned defaults date, a reverse proxy and consent-aware opt-out, identifies users by stable database IDs and resets on logout so people never merge. Key actions become explicit events, server SDKs flush before serverless functions exit, flags are bootstrapped from server evaluation to stop flicker, and replays mask inputs and private elements.

  • Growth & SEO
  • $posthog-analytics
  • 209 lines
  • 2 files
  • ~2,100 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- posthog-analytics

Installs to ~/.agents/skills/posthog-analytics/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $posthog-analytics, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Implements PostHog product analytics in web apps and backends — posthog-js and posthog-node/posthog-python setup, reverse proxy, identify/reset/group, custom events alongside autocapture, feature flags with bootstrapping and local evaluation, experiments, session replay masking, consent and opt-out, and funnel/retention insights. Use when adding PostHog to a React/Next.js or other app, events or users look wrong (duplicate persons, anonymous users), flags flicker or disagree between client and server, or someone asks for a PostHog funnel, retention or experiment setup.

Use it when

  • Installing PostHog in a web app or backend
  • Person, identify or group data looks wrong
  • Feature flags flicker or disagree between client and server
  • Configuring session replay privacy or building funnels and retention

Use something else when

  • Marketing site analytics run on Google Analytics: use ga4-analytics
  • You are designing the experiment itself: use ab-testing

How to check the result

The Activity view shows each planned event with correct properties, a two-device login lands on one person, logout does not merge users, and consent denial stops ingestion requests.

The mistake it prevents

Forgetting posthog.reset() on logout, so the next person on a shared device is merged into the previous user's profile.

~/.agents/skills/posthog-analytics/SKILL.md

--- name: posthog-analytics description: Implements PostHog product analytics in web apps and backends — posthog-js and posthog-node/posthog-python setup, reverse proxy,… license: MIT ---

Done well: each real person is one PostHog person across devices after login, key product actions are explicit events with stable names and typed properties, feature flags evaluate identically on client and server without flicker, recordings never contain secrets, and tracking respects the consent the user gave.

Before writing code

  • Read AGENTS.md, package.json/pyproject.toml, and search for existing usage: grep -rnE "posthog|phc_[A-Za-z0-9]+" --exclude-dir=node_modules .
  • Confirm region (US https://us.i.posthog.com or EU https://eu.i.posthog.com) and whether the org self-hosts.
  • Get or write the event list (taxonomy design belongs to analytics-tracking-plan).
  • Installing packages (posthog-js, posthog-node, posthog) needs network and may need approval in the sandbox.
  • The project API key (phc_...) is public by design; personal API keys and feature flag secure keys are secrets: server-side env vars only.

Workflow

- [ ] 1. Client init (with defaults date, proxy, consent mode)
- [ ] 2. Identity: identify on login, reset on logout, groups for B2B
- [ ] 3. Custom events for key actions; tune autocapture
- [ ] 4. Server-side events and flags
- [ ] 5. Feature flags and experiments without flicker
- [ ] 6. Session replay with masking
- [ ] 7. Verify in Activity and build the core insights

1. Client init

Next.js 15.3+ can initialize in instrumentation-client.ts; other React apps init once at the entry point.

// instrumentation-client.ts (Next.js) or src/analytics.ts
import posthog from "posthog-js";

posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {
  api_host: "/ingest",                 // reverse proxy, see below
  ui_host: "https://us.posthog.com",
  defaults: "2026-01-30",              // pin a defaults date; bump deliberately after reading the changelog
  person_profiles: "identified_only",
  opt_out_capturing_by_default: requiresConsent(),  // your CMP decides
});
  • defaults opts into PostHog’s current recommended behaviours as of that date (including SPA page views via capture_pageview: 'history_change'). Pinning a date keeps behaviour stable across SDK upgrades.
  • Do not call posthog.capture('$pageview') on route changes when history-based page views are on; that double counts.

Reverse proxy (reduces ad-blocker loss) for Next.js:

// next.config.js
module.exports = {
  skipTrailingSlashRedirect: true,
  async rewrites() {
    return [
      { source: "/ingest/static/:path*", destination: "https://us-assets.i.posthog.com/static/:path*" },
      { source: "/ingest/:path*", destination: "https://us.i.posthog.com/:path*" },
    ];
  },
};

2. Identity

// after successful login or signup (not on every render)
posthog.identify(user.id, { plan: user.plan, signup_date: user.createdAt });
// B2B: attach to the company so insights can aggregate by account
posthog.group("company", org.id, { name: org.name, seats: org.seats });
// on logout
posthog.reset();
  • Use the stable database ID as distinct_id, never email (emails change).
  • Call identify with the same ID on the server (distinctId) so backend events join the same person.
  • Forgetting reset() on logout merges the next user on a shared device into the previous person.
  • Person properties may hold contact details only if the privacy policy and consent allow it; never put passwords, tokens or payment data anywhere.

3. Events

  • Autocapture is useful for exploration, but build key metrics on explicit events: names are stable even when the UI changes.
  • Naming: object_verb or verb_object consistently, lowercase with spaces or underscores per the team’s convention (e.g. project_created, invite_sent). Properties snake_case, typed consistently (a number stays a number).
  • Capture outcomes server-side when the server is the source of truth (payment succeeded, subscription renewed); client-side for UI interactions.
posthog.capture("report_exported", { format: "csv", row_count: rows.length, source: "dashboard" });

4. Server side

// lib/posthog-server.ts
import { PostHog } from "posthog-node";

export function serverPostHog() {
  return new PostHog(process.env.POSTHOG_KEY!, {
    host: "https://us.i.posthog.com",
    flushAt: 1, flushInterval: 0,   // serverless: send immediately
  });
}

// in a handler
const ph = serverPostHog();
ph.capture({ distinctId: user.id, event: "subscription_renewed", properties: { plan: "pro", mrr: 49 } });
const newCheckout = await ph.isFeatureEnabled("new-checkout", user.id);
await ph.shutdown();               // flush before the function exits

Python (keyword arguments work across SDK major versions):

from posthog import Posthog
ph = Posthog(os.environ["POSTHOG_KEY"], host="https://us.i.posthog.com")
ph.capture(distinct_id=str(user.id), event="subscription_renewed", properties={"plan": "pro"})
ph.shutdown()

For high-volume flag checks, enable local evaluation by giving the server SDK a feature-flag secure key or personal API key (secret) so it evaluates without a network call per request.

5. Feature flags and experiments

import { useFeatureFlagEnabled, useFeatureFlagVariantKey } from "posthog-js/react";

const showNewNav = useFeatureFlagEnabled("new-nav");          // undefined until loaded
const pricingVariant = useFeatureFlagVariantKey("pricing-test");
  • Flags load asynchronously; undefined means “not loaded yet”, not “off”. Render a neutral state or bootstrap.
  • Avoid flicker by evaluating on the server and bootstrapping the client: posthog.init(key, { bootstrap: { distinctID: user.id, featureFlags: flagsFromServer } }).
  • Release conditions on person or group properties need those properties sent before evaluation (or passed as override properties in server evaluation).
  • Experiments: create in PostHog with a primary metric before launch; exposure is recorded when the flag is evaluated ($feature_flag_called), so evaluate it only where the variant is actually shown. Test design belongs to ab-testing.
  • Lifecycle and cleanup of flags belong to feature-flags.

6. Session replay

  • Enable in project settings; mask by default:
posthog.init(key, { /* ... */ session_recording: { maskAllInputs: true, maskTextSelector: "[data-private]" } });
  • Add the ph-no-capture class to elements that must never be recorded (payment iframes are cross-origin and not recorded anyway; card fields in your own DOM must be masked).
  • Sample or trigger recordings (by flag or URL) on high-traffic apps to control cost.

7. Verify and analyze

  • Run posthog.debug() in the browser console for verbose logging; check the Activity (live events) view for event names, properties and the person they attach to.
  • Log in on two browsers as the same user: both sessions must show under one person.
  • Log out then in as another user: no merge.
  • With consent denied: no requests to /ingest (or only what the chosen cookieless configuration allows).
  • Core insights: activation funnel (signup → key action within 7 days, conversion window set explicitly), weekly retention on a meaningful repeat action, trends on key events broken down by plan or group. Use SQL insights for anything the UI cannot express.

Gotchas

  • Calling identify with different IDs for the same person (email then UUID) creates merge problems that cannot be undone cleanly. Pick one ID scheme.
  • Ad blockers block *.posthog.com; without a proxy expect material client-side loss.
  • Server events without the identified distinctId create anonymous persons; pass the same ID as the client.
  • Bootstrapped flags must use the same distinct ID as identify, or evaluations differ.
  • Multiple posthog.init calls (e.g. in a component that re-renders) cause duplicate events; init once.

Output

**Setup:** posthog-js in instrumentation-client.ts (defaults 2026-01-30), /ingest proxy,
opt-out until consent; posthog-node helper for API routes.
**Identity:** identify on login (user.id), group "company", reset on logout.
**Events:** 9 explicit events (table: name, properties, file:line, client/server).
**Flags:** new-checkout evaluated server-side and bootstrapped; no flicker.
**Replay:** maskAllInputs, [data-private] masked, 20% sampling.
**Verified:** Activity view shows all 9; two-device test merges into one person.

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "PostHog Analytics"
  short_description: "One person per user, flags without flicker"
  default_prompt: "$posthog-analytics Add PostHog to our Next.js app with a reverse proxy and consent banner support"

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
New

Introducing Codex Skills Pro

79 professional skills that take Codex through complete engagements, each with its own scripts, references and a report that verifies the result.

All-AccessOne-time payment, no subscription
$49
Get All-AccessOr browse individual skills from $2.99 →

Secure checkout by Creem