Best Way to Sync Slack to Postgres in 2026

Intergalactic Data Labs
—
—
13 min read
If you run a community or support channel in Slack and want every message, thread, and member in a Postgres database you control, this guide is for you. The hard part is not Postgres. It is a Slack API that opens history only to bots that joined the channel, hides replies behind a second call, and since May 2025 throttles unlisted commercial apps to one history request a minute.
As of November 2026 four paths land Slack data in Postgres. A self-hosted engine such as Filament or Airbyte runs on your compute under your own app. A managed platform such as Hevo or Fivetran runs the sync and meters events or rows. A library such as dlt leaves scheduling to you, and a foreign data wrapper queries Slack live. App ownership, threads, deletes, and price decide between them.
Path | Examples | Best for | Main limit |
|---|---|---|---|
Self-hosted engine | Filament, Airbyte | Typed tables on your own compute, no row meter | You run the process and the schedule |
Managed platform | Hevo, Fivetran, Estuary, Stitch | Deletes handled for you, no servers | Metered by events, rows, or GB |
Library or CLI | dlt, ingestr, Sling, Meltano | A pipeline that lives in a repo | Replies off by default or behind a paid token |
Live foreign data wrapper | Supabase Wrappers | Ad hoc SQL over Slack | One channel per query, nothing stored |
What does the Slack API let a sync tool read?
Every path calls the same Web API, so its rules shape every tool on this page. The rate limit docs evaluate requests “per method, per workspace” in one minute windows and answer a breach with HTTP 429 and a Retry-After header.
Method | What it returns | Internal app | Unlisted commercial app |
|---|---|---|---|
Messages in one channel | Tier 3, 50 or more a minute, up to 999 a page | 1 a minute, 15 a page since May 29, 2025 | |
One thread | Tier 3, up to 1,000 a page | 1 a minute, 15 a page | |
Channels the token has joined | Tier 3 | Tier 3 | |
Every channel the token can see | Tier 2, 20 or more a minute | Tier 2 | |
Every member, deactivated included | Tier 2 | Tier 2 | |
Files, page numbered | Tier 3 | Tier 3 |
The last column reshaped this route. Slack’s May 2025 changelog cut history and replies to Tier 1 for apps sold outside the Marketplace. It states that “Internal customer-built applications are not impacted by these changes.” A token from an app you created keeps the full budget. A vendor’s unlisted app does not, which is why CloudQuery now says its “Slack source plugin no longer supports syncing message histories”.
Three more rules decide completeness. The history docs say “Only user tokens can access public channels they are not in”, so a bot reads only channels it has joined. Replies never appear in history, per the retrieving messages guide, which marks a parent by a thread_ts equal to its ts.
An edit keeps the original ts and a delete simply vanishes. Only the message_deleted event signals that “The original message will no longer return in history calls.” A sync that trusts a ts cursor sees neither.
What decides a Slack to Postgres sync?
Five criteria decide this route, and throughput is not one of them, because the per minute budget caps every tool at the same ceiling.
App ownership. Whether the sync runs on an app you created, which keeps Tier 3, or on a vendor’s unlisted app, which gets one history request a minute.
Thread coverage. Whether the tool reads
conversations.repliesat all, and how it learns that an old thread gained a reply.Edits and deletes. Whether the tool re-reads a window of history and compares it with the destination, or trusts a
tscursor and never sees either.Postgres landing. Typed columns keyed on channel and
ts,blocksandreactionsas jsonb, and an upsert so a rerun converges, per the write modes guide.The unit you pay by. Events, monthly active rows, credits, gigabytes, a flat token, or your own compute.
Which tools can sync Slack to Postgres?
Thirteen tools run Slack to Postgres, and they split on whether they catch deletes and whether they read threads at all.
Tool | Slack objects | Incremental | Edits and deletes | Pricing unit | License |
|---|---|---|---|---|---|
Team, users, groups, channels, messages, replies, files, bookmarks, pins | Messages, replies, files | Not observed | None | Apache 2.0 | |
Users, channels, members, messages, replies, reactions, files | Messages, replies | Marked by comparison | Events | Proprietary | |
30 plus tables, messages, blocks, reactions, pins, bookmarks | Replies, 30 day history re-read | Monthly re-import | Monthly active rows | Proprietary | |
Channels, members, messages, users, threads | Messages, threads | Not documented | Credits on Cloud | ELv2 | |
Channels, members, messages, users, threads, groups, files | From a start date | Not documented | GB plus per connector | BSL 1.1 | |
Channels, members, messages, threads, files, users, groups | Messages, files, users | Threads re-read in full | Rows a month | Proprietary, community tap | |
Channels, messages, users, access logs | Messages | None | None | Apache 2.0 | |
Team, users, channels, members, messages, threads | Messages | Not documented | CLI Pro token | GPLv3 | |
Channels, users, messages, access logs | Messages | None | None | FSL 1.1 | |
Channels, members, messages, threads, users | Messages | Moved threads re-read | None | Apache 2.0 | |
Users, teams, channels, files, no history | None stated | None | Rows a year | MPL 2.0 | |
Workspaces, channels, messages, users as foreign tables | Live query | Live query | None | Apache 2.0 | |
Not itemized | Not stated | Not stated | Flat plan | Proprietary |
Hevo is the one platform that documents delete detection for messages. Its Slack source page compares each fetch with the destination and sets __hevo__marked_deleted on rows the API no longer returns, though users and channels sit outside that check. It bills by events above a free tier, per its pricing page.
Fivetran’s classic Slack connector is sunset, and its changelog says every message table was removed for connections created on or after September 3, 2025. The replacement Slack App connector runs on an app you create, re-reads a month of history each sync, and re-imports monthly to catch updates and deletes, with the bot joining one channel at a time.
Airbyte’s certified source reads threads and private channels and offers a join all public channels toggle, per its docs. Airbyte is open source under ELv2. Its Postgres destination asks you to “only consider using postgres as a destination for small data volumes (e.g. less than 10GB) or for testing purposes”, and Cloud starts at 20 dollars a month.
Estuary runs the same third party connector with a threads lookback and bills “$0.50/GB + $100/mo per connector instance” above a free tier, per its pricing page, under the Business Source License.
Stitch runs the community Singer tap in beta and re-replicates a whole thread whenever one reply changes, per its integration page.
The code first tools trade coverage for control. dlt’s verified source keys a cursor on ts, keeps replies off by default, and stays Apache 2.0. ingestr has no replies table and ships under an FSL license. Sling covers threads behind a CLI Pro token, and Meltano’s tap-slack re-reads any thread whose latest reply moved.
The Supabase Slack wrapper queries Slack live rather than copying it, one channel per query. Snowflake’s Openflow connector for Slack lands in Snowflake only, and neither PeerDB nor Debezium has a Slack source.
Where Filament fits on Slack to Postgres
Filament reads Slack through an HTTP manifest under an app you create and writes typed Postgres tables, which meets the ownership, thread, and landing criteria and concedes deletes. The Slack source is alpha, a label the connector overview defines as documentation derived and not yet tested end to end, and the Postgres sink is beta. Filament is built by Galaxy, which also publishes this site.
On ownership, the source page tells you to use a token from an app you created in your own workspace, which keeps Tier 3. On threads, it lists channels through users.conversations, so a channel the bot never joined never raises not_in_channel, and it calls conversations.replies only for parents whose latest_reply passed the saved watermark.
On the landing, the sink page creates each table with a primary key from the source’s key fields, so messages key on channel_id and ts. It maps json to jsonb and upserts with ON CONFLICT (pk) DO UPDATE “so re-runs and retries are idempotent”. Rows arrive through binary COPY with a CRC32-C checksum per batch, per the integrity guide, and the engine is Apache 2.0 with no meter.
The concessions are on the page. The source states that “Edits and deletions of messages and replies are never observed”, so a deleted message keeps its row until a full replace. Every incremental run still walks the full history of every enabled channel to find threads that moved, and all channels share one messages cursor.
No public benchmark covers a Slack route, so this page carries no throughput claim.
Setting up Slack to Postgres with Filament
One Slack app, one bot token, one Postgres role, and two pipelines cover a workspace. The sink block is copied from the CLI guide. The source flags apply its --source-<field> rule to the connector’s two config keys, because the docs carry no Slack example yet, so validate them on v0.33.0.
Create the app at api.slack.com inside the workspace you want to read, which keeps it internal. Give the bot token the channels:history, channels:read, groups:history, groups:read, users:read, usergroups:read, files:read, pins:read, bookmarks:read, and team:read scopes, install it, and invite it to each channel with /invite.
Install the CLI from the installation page and create the connections. Secrets take an environment reference, which the CLI stores as env:NAME.
Discovery lists nine resources. Three carry a cursor, so the split is an hourly incremental pipeline for messages, thread replies, and files, and a nightly full pipeline for the catalog, both in upsert mode. Private channels go in through conversation_types in the config file below.
Resource | Read mode | Key | Lands as |
|---|---|---|---|
messages | Incremental on ts | channel_id, ts | text, user, thread_ts, reply_count, blocks and reactions jsonb, raw |
thread_replies | Incremental on ts | channel_id, thread_ts, ts | Same columns, one row per reply |
files | Incremental on created | id | name, mimetype, size, user, raw |
conversations, users, user_groups, team, bookmarks, pins | Full | id, or channel_id and created | Catalog tables with a raw remainder |
The same shape as a config file, checked with filament config validate per the first pipeline guide, adds private channels.
Verify from psql. The first query shows a channel the bot never joined as a missing row, the join proves threads landed, and a GIN index makes the archive searchable, per the full text search docs.
Every row also carries _filament_run_id, so a query can exclude a failed run. Schedules live in the web app, which filament up --detach opens per the web guide. Per the source docs, “Configure a lookback of at least one run’s duration on messages and thread_replies if late arrivals matter.” That override lives per resource in the pipelines API, and upsert absorbs the repeats.
Which path fits which Slack workspace?
A community or support workspace that wants typed tables on its own Postgres goes to Filament under its own app. A team that needs deleted messages marked goes to Hevo, and a team already on Fivetran stays there for the monthly re-import.
Slack workload | Path | Criterion that decides |
|---|---|---|
Support or community channels, typed tables, your own Postgres | Filament | Own app keeps Tier 3, replies by watermark, no meter |
Deleted messages must disappear downstream | Hevo Edge | Documented comparison that marks deletes |
Already paying Fivetran, want edits caught | Fivetran Slack App | 30 day re-read and monthly re-import |
Many public channels, no one to invite the bot | Airbyte | Join all public channels toggle |
Minutes fresh messages under 10 GB a month | Estuary | Free tier covers it, threads lookback |
One channel into a notebook | dlt or ingestr | One command, cursor on ts |
Ad hoc SQL over live Slack, nothing stored | Supabase Wrappers | Foreign tables, one channel per query |
Hevo wins when deletes must propagate, Airbyte when nobody will invite a bot to hundreds of channels, and Estuary on freshness under its free gigabytes. Filament takes the typed table case where the data stays on your compute, as on the Notion, GitHub, and PostHog routes into the same sink. It loses the moment a deleted message has to vanish without a full replace.
Frequently asked questions
How do you export Slack messages to a Postgres database?
Create a Slack app in your own workspace, give it the history and read scopes, invite its bot to each channel, then point a sync tool at the bot token and a Postgres connection string. The tool walks conversations.history per channel, calls conversations.replies for threads, and writes one table per object.
What is the Slack API rate limit for reading message history?
For an app built in your own workspace, conversations.history and conversations.replies sit at Tier 3, which Slack documents as 50 or more requests a minute with hundreds of messages per page. Since May 2025 a commercially distributed app outside the Slack Marketplace gets one request a minute and 15 messages per page on those two methods.
Does a Slack sync pick up edited and deleted messages?
Not through history alone. Slack keeps the original ts on an edited message and drops a deleted one from history without a marker, so a cursor on ts never sees either. Tools catch them by re-reading a window of history and comparing it with the destination, or by subscribing to the message_changed and message_deleted events.
Why does a Slack sync return not_in_channel for some channels?
A bot token reads history only for conversations the bot has joined. Slack’s docs say only user tokens can access public channels they are not in, so a bot that can list a channel still gets not_in_channel on its history until someone invites it. Private channels always need an invitation.
Are thread replies included in Slack conversation history?
No. conversations.history returns the parent message with a thread_ts and a reply count, and the replies sit behind conversations.replies, one call per thread. Slack reveals new replies on an old thread only through the parent’s latest_reply, so a sync re-reads parents to learn which threads moved.
Can Slack send data to a database without a third party tool?
Only in narrow cases. Admins can export public channel messages as JSON on every plan, and private channels and direct messages on Business+ and Enterprise. Enterprise org owners get a Discovery API that Slack restricts to security and compliance uses. Snowflake’s Openflow connector for Slack lands in Snowflake, not Postgres.
Which Slack objects does Filament load into Postgres?
Team, users, user groups, conversations, messages, thread replies, files, bookmarks, and pins, each as a typed table with the unprojected JSON kept in a jsonb column. Messages, thread replies, and files read incrementally, and the rest are re-read in full every run. Edits and deletions are not observed.
How often should a Slack to Postgres pipeline run?
Hourly for messages and threads, nightly for users, channels, and files. Every incremental run re-walks the full history of each enabled channel to find threads that moved. Filament’s docs put that walk at about one request per thousand messages, so a busy workspace spends much of its budget on it.
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.