The Postgres sink lands your tables as plain tables in a PostgreSQL database you manage. Unlike every other sink it is not append-only: inserts and updates become INSERT … ON CONFLICT DO UPDATE, and a delete physically removes the row. There is no tombstone, no history column, and no configurable write mode — the table always holds current state.
Supported deployments
Anywhere the INSERT … ON CONFLICT form is available, which is every PostgreSQL 9.5 or later: self-hosted, Amazon RDS, Amazon Aurora, Google Cloud SQL, Azure Database for PostgreSQL, Supabase, and Neon. The sink only needs a writable database, so a connection-pooler endpoint is fine here — unlike the Postgres source, which needs a direct connection for logical replication.
Prerequisites
- A reachable PostgreSQL server and a database to land in.
- A login that can connect and create schema objects, because Fabric issues
CREATE SCHEMA IF NOT EXISTS,CREATE TABLE IF NOT EXISTS, andALTER TABLE … ADD COLUMNas well as the upserts and deletes:
CREATE ROLE fabric_writer LOGIN PASSWORD '<secret>';
GRANT CONNECT ON DATABASE analytics TO fabric_writer;
GRANT CREATE ON DATABASE analytics TO fabric_writer; -- CREATE SCHEMA
-- If writing into a schema Fabric does not create:
GRANT USAGE, CREATE ON SCHEMA public TO fabric_writer;
GRANT SELECT, INSERT, UPDATE, DELETE
ON ALL TABLES IN SCHEMA public TO fabric_writer;When Fabric creates the schema and tables itself it owns them, so no extra grants are needed on those objects.
Connection fields
| Field | Required | Default | Notes |
|---|---|---|---|
| Name | Yes | — | Unique across all destinations. |
| Host | Yes | — | |
| Port | Yes | 5432 | |
| Database | Yes | — | The database the tables land in. |
| Username | Yes | — | |
| Password | Yes on create | — | Leave blank on edit to keep the stored password. |
How tables land
The schema and table names are not destination fields. They come from the sync's own namespace and table name: <database>.<namespace>.<table>, with the namespace defaulting to raw. In the table editor that field is labeled Postgres schema.
- On first write, Fabric creates the schema and table if missing. The sync's identifier columns become a real
PRIMARY KEY— which is what makes the upsert work. - One metadata column is added,
_synced_at. The append-only columns (__op,__deleted,__table, …) are deliberately absent. - Schema evolution is additive: a new column is added nullable, never retyped or renamed.
- Deletes are destructive — the row is gone, with no history to recover it.
Large columns and REPLICA IDENTITY
Postgres does not resend a large (out-of-line TOASTed) column that an UPDATE leaves untouched. Fabric handles that so every row still lands complete, and no new column is added — readers merge exactly as before.
- With REPLICA IDENTITY FULL on the source table (set with
ALTER TABLE <table> REPLICA IDENTITY FULL), Fabric takes the unchanged value from the logged old row — no extra reads. Recommended for tables with large text,jsonb, orbyteacolumns; the setup check warns when a table has such columns without it. - Without it, Fabric reads the missing columns back from the source database by key — one query per table per commit — and rows still land complete. The value read is the source's value at commit time, which may be newer than the event.
- The Postgres destination keeps its stored value for the untouched column and performs no reads.
Gotchas
Watch out
- Test connection runs only
SELECT 1. It proves reachability and credentials, not that the login can create schemas or write — verify those with the first sync. - The upsert needs the primary key to exist. Fabric creates it when it creates the table; a pre-existing table without a unique constraint over the identifier columns fails at write time.
- There is no TLS control in the form — the connection uses libpq's default (
sslmode=prefer). Managed Postgres with a strict TLS policy needs a deployment-level workaround. - Source columns differing only by case collapse to one Postgres column, and a column named like a metadata field is quarantined.