Best Way to Sync PostHog to Postgres (2026)

Intergalactic Data Labs
โ
โ
11 min read
PostHog's own batch export is the only supported way to land raw events in Postgres, and a connector tool carries the entities around them. Eight paths work as of October 2026, split by whether they move events, entities, or both, and by whether you pay per row, per active row, or run the reader yourself.
Path | Best for | Main limit |
|---|---|---|
Filament | Persons, cohorts, flags, experiments, and surveys as typed tables | No events, persons re-read in full each run |
PostHog batch export | Raw events, persons, and sessions on a schedule | Per-row billing past the free million, no flags or cohorts |
Fivetran PostHog Lite | One managed connector for events and entities | Active-row billing, PostHog Cloud only |
Airbyte | Self-hosted events plus persons and flags | Events stream reads the deprecated endpoint |
Estuary | Events and sessions refreshed every five minutes | Reads events through the query endpoint PostHog restricts |
ingestr | One-command CLI copy of persons, flags, and events | FSL license, persons paged in full |
Sling | Scripted pulls inside a wider Sling setup | API connections need a CLI Pro token |
Plain script | Anything the project API exposes | You own paging, retries, and rate limits |
The split exists because PostHog deprecated its events API and tells third-party connectors to use batch exports, while the persons endpoint has no modified-since filter. Of Fivetran, Airbyte, and similar tools, PostHog's docs say "we do not recommend using those to move data from PostHog to other destinations" (PostHog, ETL tools).
The five facts that decide this sync
Five facts about PostHog's data model and API decide which path fits.
Events and entities are different shapes. Events are immutable, high volume, and stored in ClickHouse, "our main analytics backend" in PostHog's words (PostHog, how ClickHouse is used), while persons, cohorts, and flags are small and mutable (PostHog, batch exports).
The API has a hard hourly ceiling. Analytics endpoints allow 240 requests a minute and 1,200 an hour per personal API key, with no higher tier offered (PostHog, API overview).
Persons cannot be read incrementally. The persons list filters by distinct ID, email, properties, and search text, never by modification time (PostHog, persons API).
Billing units differ by an order of magnitude, from rows exported or monthly active rows to nothing when you run the reader yourself.
Connection direction matters. A batch export connects from PostHog into your database over SSL with allowlisted IPs (PostHog, Postgres destination), while a self-run reader needs only outbound HTTPS.
How can you sync PostHog to Postgres?
Batch exports own the event stream, and every other path competes on the entity tables. The at-a-glance table shows what each path moves and what it costs.
Path | Events | Persons and flags | Billing unit | Runs where |
|---|---|---|---|---|
Filament | Yes | 13 entity resources | None | Your host |
PostHog batch export | Yes | Persons and sessions only | Rows exported | PostHog cloud |
Fivetran PostHog Lite | Via batch export endpoint | Yes, persons re-imported fully | Monthly active rows | Fivetran cloud |
Airbyte | Yes, deprecated endpoint | Persons, flags, cohorts, insights | Self-hosted or credits | Your host or Airbyte Cloud |
Estuary | Yes, query endpoint | Persons, cohorts, flags | GB plus per connector | Estuary cloud |
ingestr | Yes, append | Persons, flags, cohorts | None | Your host |
Sling | Yes, incremental | Persons, flags, cohorts | CLI Pro token | Your host |
Plain script | Deprecated endpoint | Anything exposed | None | Your host |
PostHog batch exports write three models, events, persons, and sessions, into your Postgres on a 5 minute to weekly schedule, with backfills (PostHog, batch exports). It reads ClickHouse directly, so no API ceiling applies. The limits are duplicates from overlapping backfills, new fields not added to existing tables, and no flags, cohorts, or experiments.
Fivetran lists PostHog as a Lite connector and swapped its EVENT table for BATCH_EXPORT_EVENT in July 2026 after PostHog discontinued the event endpoint (Fivetran, PostHog changelog). It syncs cohorts, flags, and insights incrementally and re-imports persons every sync (Fivetran, PostHog sync overview). It needs PostHog Cloud, so self-hosted is out, and it bills on monthly active rows (Fivetran, pricing).
Airbyte's PostHog source carries seven streams, with events incremental and persons, cohorts, flags, and insights full (Airbyte, PostHog source), community supported under the Elastic License (Airbyte, source metadata). It is one self-hosted connector for both shapes. Its events stream polls the deprecated endpoint, and the Postgres destination is advised for "small data volumes (e.g. less than 10GB)" (Airbyte, Postgres destination).
Estuary's capture snapshots persons and cohorts and reads flags, events, and sessions incrementally through the query endpoint every 5 minutes by default (Estuary, PostHog capture). That gives near-live events, but PostHog says connectors built on that endpoint "are not supported and will be rate-limited or rejected" (PostHog, query API). The connectors are BSL licensed (Estuary, LICENSE) and billed per gigabyte plus a monthly fee per connector (Estuary, pricing).
ingestr copies persons, flags, cohorts, events, and annotations with one command, merging persons on last seen time and appending events by timestamp (ingestr, PostHog source). Its license is the Functional Source License with a future Apache grant (ingestr, LICENSE), and the persons merge still pages the full list.
Sling exposes persons, events, cohorts, and flags as an API connection, with events incremental by timestamp, but "APIs require a CLI Pro token or Platform Plan" (Sling, PostHog connection). The CLI is GPLv3 (Sling, LICENSE). A plain script works for any resource, and dlt ships no verified PostHog source, so a dlt route is that same hand-written pull. Stitch and Hevo list no PostHog connector.
Where Filament fits on PostHog to Postgres
Filament carries the entity tables and leaves the event stream to the batch export. Its PostHog source reads 13 default resources, from persons and feature flags to experiments and surveys, and the docs state plainly that "Events are excluded" (Filament, PostHog source). Both connectors on this route are labeled beta (Filament, connector maturity). Filament is built by Galaxy, which also publishes this site.
Against the five facts above, the case is about control, not speed. No public benchmark run covers a PostHog route, and this series makes no speed claim without one. Each resource lands as its own typed table with primary keys preserved and JSON stored as jsonb, and re-runs upsert with ON CONFLICT (pk) DO UPDATE (Filament, Postgres sink). Schema evolution, once enabled, adds new source columns and never drops or retypes one.
Every batch carries a CRC32-C checksum that the sink recomputes before the COPY, and a failed run leaves progress resumable (Filament, integrity and checkpoints). The reader runs on your host, so no inbound route opens and no row meter runs.
The documented limits are real. Insights and session recordings are the only incremental resources, persons are "a full snapshot" on every run, and incremental reads do not propagate deletions (Filament, PostHog source). A static limiter of 0.3 requests per second keeps a run under the hourly bucket and sets the arithmetic below.
Persons in the project | Pages at 100 per page | Time at 0.3 requests a second |
|---|---|---|
10,000 | 100 | About 6 minutes |
100,000 | 1,000 | About 56 minutes |
1,000,000 | 10,000 | About 9 hours |
These times are computed from the documented limiter and page size, not measured. Detail fetches for dashboards and experiments add one request per object.
How to sync PostHog to Postgres with Filament
One pipeline carries the entity tables on a nightly cron, and a PostHog batch export carries events into the same database. The commands follow the documented flag rule, where each connector field becomes a --source- or --sink- flag with underscores turned to hyphens (Filament, CLI guide). The docs show no PostHog example, so this sequence is assembled from that rule and is worth a filament source create --help check on v0.24.0.
Install the CLI, create a PostHog personal API key with read scopes for the resources you want, and export the key and your Postgres DSN as environment variables.
Register the project as a source. The host is the app origin, never the us.i.posthog.com ingestion host, and the project ID is the number after /project/ in the app URL.
Register the sink, then run discovery to confirm the resources resolve with your key's scopes.
Create the pipeline with upsert writes into a posthog schema and run it once.
The same setup as YAML in ~/.config/filament/filament.yaml.
Verify from psql. Each table carries a raw column holding the fields the manifest did not project.
Schedule it. A pipeline takes one cron schedule with a timezone and an overlap policy, and SKIP stops a slow full read from stacking on itself (Filament, pipelines API).
Then create a PostHog batch export for events into the same database. Deduplicate on the event UUID, since overlapping backfills produce duplicates, and join on distinct_id rather than the event's person ID, which PostHog's changelog warns is often stale (PostHog, changelog).
The right path for each PostHog job
Land events with the batch export, and land everything else with a reader you run.
PostHog data you need | Path | Why it wins |
|---|---|---|
Raw events, kept past retention | PostHog batch export | Only supported event path, reads ClickHouse, backfills |
Flags, cohorts, experiments, surveys as SQL tables | Filament | Typed tables, add-only schema changes, no row meter, runs in your network |
Events and entities from one managed vendor | Fivetran PostHog Lite | Batch export endpoint plus entity tables, if Cloud and MAR fit |
Self-hosted PostHog with events and entities | Airbyte | Self-hosted, both shapes, accepts the deprecated endpoint risk |
Sub-hourly events into a stream platform | Estuary | Five minute captures and many downstream materializations |
A one-off copy from a laptop | ingestr | One command, no server |
Fivetran wins when a team already pays for it and wants no extra reader. Airbyte wins for self-hosted PostHog, where neither the batch export nor Fivetran apply. Estuary wins when events must fan out beyond Postgres. Filament is the pick for the engineer who owns the Postgres, needs the entity tables typed and current, and will not pay by the row for them.
Related guides
Best Way to Sync GitHub to Postgres (2026), the same shape for engineering data.
Best Way to Sync Notion to Postgres (2026), the next manifest connector route.
Best Way to Sync Granola to Postgres (2026), another API with no modified-since filter.
REST API to Postgres with a YAML manifest, how manifest connectors are built.
Append vs Replace vs Upsert, the write modes used above.
Introducing Filament, the announcement post.
Frequently asked questions
Can PostHog export events directly to Postgres?
Yes. PostHog batch exports write the events, persons, and sessions models to a Postgres table you own over SSL, on a schedule from every five minutes to weekly, with backfills. PostHog deprecated its events API and points connectors at batch exports, so this is the only supported path for raw events.
How much does a PostHog batch export to Postgres cost?
PostHog bills data pipelines per row exported. The first million rows each month are free, the next tier costs a fraction of a cent per row, and the rate falls at higher volumes. The receiving Postgres is yours, so the only other cost is the storage it uses.
What is the PostHog API rate limit for exports?
Analytics endpoints such as persons, insights, and session recordings allow 240 requests a minute and 1,200 an hour per personal API key. PostHog says it does not offer higher limits, so any connector that reads persons has to pace itself under the hourly ceiling or the run fails.
Can you sync PostHog persons incrementally?
No. The persons endpoint filters by distinct ID, email, properties, and search text but has no modified-since parameter, so every tool that copies persons reads the full list each run. Only the batch export persons model, which PostHog runs from its own ClickHouse, avoids the API entirely.
Why does PostHog recommend against Fivetran and Airbyte for exports?
PostHog's docs call those connectors unofficial and do not recommend them for moving data to a warehouse, because they poll an API built for the app, not for bulk export. The events endpoint is deprecated and the query endpoint rejects bulk exports. Batch exports read ClickHouse directly and are the supported route.
Which PostHog data can you get into Postgres besides events?
Persons, cohorts, feature flags, experiments, surveys, insights, dashboards, actions, annotations, notebooks, and session recording metadata are all exposed by the project API. Connector tools copy some or all of them as tables. Replay media and fresh insight calculations are not available, only metadata and cached results.
Does Filament sync PostHog events to Postgres?
No. The Filament PostHog source reads thirteen entity resources, including persons, cohorts, feature flags, experiments, and surveys, and its documentation says events are excluded. Pair it with a PostHog batch export for the event stream. Insights and session recordings read incrementally, and everything else is a full read each run.
How long does a Filament PostHog run take?
Filament paces PostHog requests at 0.3 per second to stay under the hourly analytics bucket, reading a page of persons per request. A project with 100,000 persons takes about an hour and a million takes most of a working day, before detail fetches for dashboards and experiments. Interrupted runs resume after the last completed page.
More articles
Questions
Answered
FAQ
What does Galaxy do?
What is Filament?
What is enterprise context management?
What does working with Galaxy look like?
How do you handle security and compliance?
Why does Galaxy build in the open?
Company
Copyright ยฉ 2026 Galaxy. All rights reserved.