Goal: npx posthog-definitions apply synchronizes dashboards (with inline insights) from posthog/dashboards/*.ts to a single PostHog project.
Success criterion: a developer can write a dashboard file, run apply, and see it in the PostHog UI. Re-running is a no-op. Editing the file and re-running updates the dashboard.
| Feature | Why deferred |
|---|---|
dev watch mode |
Useful but not required to prove the loop works. Add after apply is solid. |
pull command |
Requires the same diff logic in reverse; ship apply first. |
| Delete on removal | Risky default. Ship --prune later, opt-in only. |
| Multi-environment configs | One project per CLI invocation covers the common case via --project. |
| Feature flags, actions, cohorts | Different data models. Each is a separate sprint. |
Query types beyond trends and hogql |
Each is a typed wrapper, additive. Trends covers ~70% of demo cases. |
| Drift detection on UI edits | Strictly post-MVP; needs a UX decision. |
| Automated test suite | MVP verifies by running apply against a dev project. Backfill tests once the loop is shipping. |
The order matters: each step unblocks the next. The order is also designed so that the riskiest unknowns are verified earliest.
Read-only investigation. No code yet.
- Verify tag round-trip. Hit
POST /api/projects/:id/dashboards/with a tag, thenGET, confirm the tag comes back. ConfirmPATCHpreserves and updates tags. - Verify nested tile write. Confirm
DashboardSerializer.createaccepts thetiles: [{ insight: {...} }]shape and creates the insight inline, or whether we need a two-step (insight first, then dashboard). - Verify tag-based search. Confirm we can query dashboards/insights by tag prefix. If not, fall back to fetching all and filtering client-side.
These can also be verified inline during implementation by running apply against a dev project — the first real run is the test.
- Repo scaffold:
package.json,tsconfig.json,src/,bin/posthog-definitions. Single package with both SDK and CLI exports. - SDK types: copy/derive
Dashboard,Insight,Tile,Layoutfrom this docs spec. Adddashboard,insight,textfactory functions (bare nouns, nodefine*prefix). Pure, no I/O. - API client: hand-rolled minimal typed wrapper over the dashboards + insights endpoints. Swap for a generated client post-MVP if useful.
- Loader: glob
posthog/**/*.ts, import viatsx(programmatic), collect default exports, tag each withpath. - Validator: required fields, unique keys per kind, layout bounds, tile non-emptiness.
- Differ: pair desired vs current by tag. Emit ops:
{ kind: "create" | "update" | "unchanged"; resource: ...; payload?: ... }. - Executor: serial HTTP calls in order (insights first, dashboards second), stop on first error. Re-check
iac:*tag immediately before every write (safety guard). applycommand wiring: load → validate → diff → execute → report.
--dry-run: short-circuit before any mutation, print the planned ops.--verbose: log each call.- Exit codes per spec.
Manual: run apply against a dev project with .envrc-loaded credentials. Confirm idempotence (second run = all unchanged), mutation (edit a field, re-run = one update), and the safety invariant (a hand-built dashboard alongside is untouched). Backfill automated tests after the first ship.
- Update
docs/interface/getting-started.mdwith real installation instructions. - Tag
v0.1.0. Publish to npm under@posthog/definitions(private at first). - Announce in a small group; gather feedback before public release.
Most expensive parts in descending order:
- API client correctness — even hand-rolled, the dashboards endpoint has a lot of shape. Plan for some iteration against a real project.
tsxprogrammatic loader — loading user TS code at runtime has rough edges (path aliases,node_modulesresolution).- Differ correctness on
tiles— the trickiest data structure to compare. Order matters? Insight identity matters? Hash-based skip is the safety net. - Everything else — SDK types, CLI scaffold, executor — straightforward.
- A new user can install, write
posthog/dashboards/foo.ts, runapply, and see the dashboard in PostHog. - Re-running with no changes prints
0 created, 0 updated, 1 unchanged. - Editing the file changes
1 unchangedto1 updated. - A hand-built dashboard in the same project is byte-identical before and after
apply(safety invariant — verified manually against a dev project). - Docs in this repo match the shipped behavior.