Part of Growth architecture and operations.
PostHog is configured via a Public-API-driven sync script — not through the PostHog UI. Every dashboard, insight, and cohort the GTM motion depends on is a JSON file in this directory. The sync tool reconciles JSON ↔ PostHog. Git is the source of truth.
| Dashboard | Answers |
|---|---|
| Growth · Quick overview | Which acquisition, docs, demo and public runtime signals are present? Are runtime events missing transport? |
| Growth · Acquisition and demo engagement | Which landing pages, install-dialog actions, forms, docs capabilities and independent demo milestones receive activity? |
| Growth · Runtime diagnostics | Which transports produce runtime instances, requests, stream starts, ends and errors? |
These are event counts, not a joined developer conversion funnel. Install command
copy attempts are recorded before the clipboard operation; they are not completed
npm installs. Form acceptance is observed by the browser, not proof of a unique,
qualified or email-authorized contact. Runtime counts include the canonical demo
and public, unauthenticated SDK telemetry. Missing and unknown transport are not
real adapters. Historical malformed events remain visible.
Install observations, development announcements, activation, enrichment, email
eligibility and delivery outcomes remain authoritative in Neon. Use
npm run growth:report -- funnel --from <UTC> --to <UTC> and
npm run growth:report -- journey --contact <UUID> through the
Growth operations guide. No Neon export is added
by these dashboards. The untracked legacy package dashboard uses the retired
tplane:postinstall event and does not measure the current install flow.
tools/posthog/
├── project.json # Nx project (posthog-tools)
├── env.ts # zod-validated env parsing
├── client.ts # openapi-fetch wrapper
├── schema.ts # zod schemas for local JSON
├── sync.ts # CLI: plan / apply / writeback
├── report.ts # CLI: pull insights → markdown
├── live-quality.ts # CLI: sample recent events and validate payload quality
├── *.spec.ts # tests
├── types/posthog-api.gen.ts # generated from PostHog OpenAPI spec
├── scripts/generate-types.ts # regenerate the above
├── dashboards/*.json # one JSON per dashboard
├── insights/*.json # reusable insight specs
└── cohorts/ # currently empty; populated post-1A
All commands wrap nx run posthog-tools:*. Root-package aliases:
npm run posthog:sync # → nx run posthog-tools:sync:plan
npm run posthog:apply # → nx run posthog-tools:sync:apply
npm run posthog:report # → nx run posthog-tools:report
npm run posthog:quality # → nx run posthog-tools:quality:live
npm run posthog:generate-types # → regenerate types/posthog-api.gen.tsDirect Nx invocations work too:
nx run posthog-tools:sync:plan
nx run posthog-tools:sync:apply
nx run posthog-tools:sync:apply --args="--delete-orphans"
nx run posthog-tools:quality:live -- --days 7 --limit-per-event 25
nx run posthog-tools:test
nx run posthog-tools:lintRequires a Personal API Key with dashboard:write, insight:write, cohort:write, project:read scopes. Create one at https://us.posthog.com/me/settings#personal-api-keys.
Env vars (see .env.example at repo root):
| Variable | Purpose |
|---|---|
POSTHOG_PERSONAL_API_KEY |
Write-scoped Personal API Key for local --apply and --report |
POSTHOG_PERSONAL_API_KEY_READONLY |
Read-only Personal API Key for CI --plan and live quality checks |
POSTHOG_HOST |
https://us.i.posthog.com (default) or your region |
POSTHOG_PROJECT_ID |
Numeric project id (visible in PostHog URL) |
CI maps POSTHOG_PERSONAL_API_KEY_READONLY into the tool's POSTHOG_PERSONAL_API_KEY environment variable for read-only --plan and live quality checks. Local development continues using the write-scoped POSTHOG_PERSONAL_API_KEY for --apply and --report.
// Example trend definition
{
"slug": "install-command-clicks",
"posthog_id": null,
"name": "Install command copy attempts",
"kind": "trends",
"interval": "day",
"events": [
{ "event": "marketing:cta_click", "math": "total",
"properties": [{ "key": "cta_id", "value": "hero_install" }] }
]
}Event names must match docs/gtm/taxonomy.md. The taxonomy.spec.ts test enforces this on every CI run.
telemetry-contract.ts is the machine-readable event/property contract used by tests and live checks.
taxonomy.spec.tsandtelemetry-contract.spec.tsguard committed dashboard JSON against undocumented events, unsupported breakdowns, unsupported filters, runtime dashboard coverage drift, and forbidden sensitive runtime fields.npm run posthog:quality -- --days 7 --limit-per-event 25samples recent live PostHog events and validates observed payloads against the same contract. It exits non-zero for missing required properties or forbidden sensitive properties, and prints warnings for non-contract fields.npm run posthog:quality -- --days 7 --limit-per-event 100 --require-critical-coveragealso requires recent samples for critical browser and runtime events. The scheduledPostHog telemetry qualityworkflow runs this thresholded seven-day check daily and supports manual dispatch.- The live workflow requires Actions secrets named
POSTHOG_PERSONAL_API_KEY_READONLYandPOSTHOG_PROJECT_ID. - Counts are bounded samples per event name, not a traffic census. A zero count does not establish healthy collection. Use
--days 1for a current-day comparison without replacing the seven-day audit. Historical malformed events can continue to fail that audit after a fix ships; do not delete them or infer a lifecycle outage from this alone. - Public SDK admission uses
libs/telemetry/src/shared/ingest.tsat Node capture and website ingest: known events, required runtime transport/browser-chat surface, bounded primitive metadata, and a 16 KiB HTTP body limit. Unknown properties are dropped and malformed known values rejected. This validates shape, not caller identity. Legacy Node stream helpers reportunknownwhen transport is omitted.
--plan— diff against PostHog, no writes. Outputs[create] [update] [orphan]per artifact. CI runs this on every PR that affectsposthog-tools.--apply— upsert via PATCH. Re-running preserves object IDs but still writes managed metadata. Membership reconciliation detaches stale tiles from managed dashboards and preserves memberships in unrelated dashboards. Wiring failures make the command fail.--apply --delete-orphans— explicit deletion of remote artifacts that have no local JSON. Never automatic.posthog_idwriteback — first successful create writes the assigned PostHog id back into the JSON. Include these IDs with the finished dashboard change.
The weekly report fetches details for repository-managed dashboards and separates
additive daily trend series over the last 28 complete
UTC days, excluding today's partial bucket. Funnels, unique counts,
breakdowns and missing/stale/incomplete results render as Unavailable with a reason;
they are never silently converted to zero. Inspect those insights in PostHog.
To rename without losing the PostHog id:
- Edit the
slugfield in the JSON, keepingposthog_idunchanged. - Do not move the file — the file path is the slug source.
npm run posthog:syncwill detect this as an update, not a create + orphan.
PostHog publishes their full Public API as OpenAPI 3 at https://us.posthog.com/api/schema/. We commit the generated TypeScript types to avoid network calls at build time. Refresh quarterly:
npm run posthog:generate-typesReview the diff carefully — field renames in PostHog's API will surface here.
git blameanswers "who changed this metric and why."- No clicking through the PostHog UI ("api/cli-first" actually delivers).
- Reproducible on a fresh PostHog project for staging/test envs.
- Reviewable in PRs like any other change.
taxonomy.spec.tsprevents dashboards from referencing events the taxonomy doesn't document.
- gtm.md — durable strategy
- docs/gtm/taxonomy.md — event names
- cowork/gtm/SKILL.md — operates this CLI weekly