PostHog

Track-only AnalyticsSink that forwards events to PostHog's /capture endpoint — compose it with a second sink that owns reads.

@voltro/plugin-posthog is a track-only AnalyticsSink — it forwards every track() to PostHog's /capture endpoint so your events show up in PostHog's product-analytics UI (funnels, cohorts, sessions, feature flags). PostHog's query model is too PostHog-specific to fit the cross-provider contract, so aggregate / timeseries / topN return AnalyticsCapabilityNotSupported. The intended wiring is therefore composeAnalytics([...]): a primary sink owns the typed reads, PostHog additionally mirrors every event. For the shared AnalyticsSink API and the useAnalytics() read methods, see Analytics & warehouse sinks.

Install

pnpm add @voltro/plugin-posthog

Wiring — compose with a read-capable sink

PostHog can't serve reads, so pair it with a primary sink via composeAnalytics (track() fans out to both; reads route to the first sink that supports them):

// app.config.ts
import { composeAnalytics } from '@voltro/runtime'
import { postgresAnalytics } from '@voltro/plugin-analytics-postgres'
import { posthogAnalytics }  from '@voltro/plugin-posthog'

export default {
  type:      'api' as const,
  name:      'myApi',
  store:     'postgres' as const,
  analytics: composeAnalytics([
    postgresAnalytics(),                                     // primary — handles reads
    posthogAnalytics({ apiKey: process.env.POSTHOG_KEY! }), // also forwards every track()
  ]),
}

Options

posthogAnalytics(options):

Option Type Default Notes
apiKey string — (required) PostHog project API key.
host string 'https://app.posthog.com' Use 'https://eu.posthog.com' for EU cloud, or your self-hosted URL.
batch { maxSize?, flushIntervalMs? } unset (immediate mode) Opt into client-side batching to PostHog's /batch/ endpoint.
batch.maxSize number 20 Flush once the buffer reaches this many events.
batch.flushIntervalMs number 5000 Flush a non-empty buffer this many ms after the first event lands.

Connection details come from options, not env. The plugin reads no POSTHOG_* variables itself — the example wires process.env into the options. Use @voltro/env's defineEnv to declare the key as a secret.

Delivery semantics

  • Immediate (default, batch unset): every track() is one /capture/ POST, so a successful track() means the event was delivered.
  • Batched (batch: {...}): events buffer and flush by size, on a timer, and on graceful shutdown (the dispose hook). A successful track() then means "buffered" — far fewer round-trips under load, but a flush failure is logged + the batch dropped (best-effort; /batch/ carries no idempotency key). A hard crash loses whatever is still buffered. Leave batch unset when you need per-event delivery confirmation.

Capability limits

aggregate, timeseries, and topN always return AnalyticsCapabilityNotSupported (provider posthog) — catch it with Effect.catchTag for graceful fallback, or rely on composeAnalytics routing reads to a read-capable sink. For PostHog-specific endpoints (capture batches, decide, person properties) import the PostHog Tag and use the raw HTTP API.

See also

  • Analytics & warehouse sinks — the shared AnalyticsSink contract, the useAnalytics() read API, composeAnalytics, and capability-not-supported error handling.