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-posthogWiring — 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 noPOSTHOG_*variables itself — the example wiresprocess.envinto the options. Use@voltro/env'sdefineEnvto declare the key as a secret.
Delivery semantics
- Immediate (default,
batchunset): everytrack()is one/capture/POST, so a successfultrack()means the event was delivered. - Batched (
batch: {...}): events buffer and flush by size, on a timer, and on graceful shutdown (thedisposehook). A successfultrack()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. Leavebatchunset 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
AnalyticsSinkcontract, theuseAnalytics()read API,composeAnalytics, and capability-not-supported error handling.