feat: add PostHog emulator#88
Open
tmchow wants to merge 1 commit intovercel-labs:mainfrom
Open
Conversation
Local emulation of PostHog event capture and feature flag evaluation, plus a tabbed inspector UI for browsing captured events and configured flags. Mirrors the structural pattern of the Resend emulator. - POST /capture/, /batch/, /e/, /track/: accepts JSON, form-encoded, text/plain (sendBeacon), and gzipped (posthog-node default) bodies. Validates the project token from body.api_key, body.token, or properties.token (browser SDK pattern). - POST /decide/ and /flags/?v=2: evaluates feature flags against distinct_id overrides and person_property conditions (exact, is_set, icontains, regex). Returns full safe-default response shape so posthog-js does not hang on missing fields. Both routes are aliased; current SDKs use /flags/. - GET /_inspector: events and flags tabs via renderInspectorPage. Out of scope: session replay, insights/queries, cohorts, surveys, group property evaluation, percentage rollouts, admin REST API. Tests cover capture (single, batched, bad token, form-encoded, text/plain, gzipped, browser SDK properties.token), cross-tenant isolation, /flags vs /decide parity, flag default and override paths, person_property condition evaluation, decide safe defaults, and inspector rendering.
Contributor
|
@tmchow is attempting to deploy a commit to the Vercel Labs Team on Vercel. A member of the Team first needs to authorize it. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a PostHog emulator with stateful event capture, feature flag evaluation, and a tabbed inspector UI for browsing captured events and configured flags. Mirrors the structural pattern of the Resend emulator.
Why this matters
PostHog is the analytics tier of the modern Vercel-deployed stack: product events plus feature flag evaluation. emulate has zero coverage for that category today (the existing services are auth, payments, email, and storage). This PR opens the analytics class.
The pain it solves:
user_signed_upwith the right properties?" today have three bad options: stubfetch(brittle, hides serialization bugs), use the SDK's built-in mock (limited shape), or hit a real PostHog Cloud project (slow, requires network, leaks data into production analytics)./flags/?v=2(or legacy/decide/). Most CI pipelines either bypass flags or hit real PostHog, both of which lose deterministic flag-gated UX testing.posthog-js and posthog-node work against the emulator out of the box, including the default gzipped batch path and the browser SDK's
?compression=gzip-js+properties.tokenshape.Demo
Inspector at
/_inspectorshowing empty events tab, then events captured after a fewPOST /capture/calls, then the configured feature flags tab.Changes
New package
@emulators/posthog(entities, store, helpers, plugin export, flag evaluator, three route files, vitest suite). Wired intopackages/emulate/src/registry.tsandpackages/emulate/package.json. RootREADME.mdand a newskills/posthog/SKILL.mddocument usage.The architectural decisions worth flagging:
api_key,token, orproperties.tokendepending on SDK), not inAuthorization. The framework'sauthMiddlewareis Bearer-only, so capture and decide validate per-route after parsing the body. Lookup precedence isbody.api_key, thenbody.token, thenproperties.token(the browser SDK pattern).conditions: [{ property, operator, value, variant }]evaluated againstperson_properties(exact,is_set,icontains,regex), plusoverrides: { distinct_id: variant }. This is the minimum eval shape that letsposthog-node+posthog-jstest flag-gated UX deterministically.application/x-www-form-urlencodedwithdata=<urlencoded-json>(sendBeacon legacy),text/plain(sendBeacon current), gzip viaContent-Encoding: gzip(posthog-node default on Node 22), and gzip via?compression=gzip-jsquery param (posthog-js default)./decide/and/flags/aliased. Current SDKs use/flags/?v=2;/decide/is kept for older clients. Same handler.sessionRecording: false,supportedCompression: [],surveys: false,toolbarParams: {}, etc. Without these fields someposthog-jsversions enter an infinite-loading state.Out of scope
Session replay, insights/queries, cohorts, surveys, group property evaluation, percentage rollouts, the
/api/projects/:id/...admin REST API, and/engage/. These are larger surfaces or niche for CI; deferring them keeps this PR shippable.Testing
15 vitest tests covering capture (single, batched, bad token, form-encoded, text/plain, header gzip, query-param gzip, browser SDK
properties.token), cross-tenant isolation,/flags/?v=2parity with/decide/, flag default and override paths, person-property condition matching, decide safe defaults, and inspector rendering.Lint, type-check, format-check, sync-versions check, and full workspace build all pass locally.