Why Supapower
Supabase gives an app Postgres, auth, RLS and realtime, but no story for reading and writing while offline. The usual answer is a second sync service in front of the database, with its own deployment, its own authorization rules and its own bill. Supapower takes the other route: treat the Data API and Realtime as the sync protocol and keep a real Postgres on the client.
The six things that make it different
Section titled “The six things that make it different”Nothing new to deploy
Section titled “Nothing new to deploy”Supabase’s Data API and Realtime are the sync protocol - no sync service, no replication slot, no second bill. There is no logical-replication slot to open on the Supabase database, no service to run in CI, staging and production, and no extra vendor sitting in the data path. See how it works for the mechanics.
Real Postgres on both ends
Section titled “Real Postgres on both ends”The client is PGlite - Electric SQL’s WASM build of Postgres - not SQLite: the same SQL, the same types, and migrations you can lift from the server almost as-is. The tradeoff is size: a WASM Postgres is heavier to ship than an embedded SQLite. See database migrations for the schema relaxations that keep a client schema forward and backward compatible with the server’s.
Your RLS policies are the sync rules
Section titled “Your RLS policies are the sync rules”Downloads and writes go through PostgREST as the signed-in user, so there is no second
authorization language to keep in step with your policies. A client can only download what its RLS
policies allow and can only write what they allow - a denied write surfaces as update_ignored or
delete_ignored rather than silently succeeding, see the error event.
Tables that should sync to signed-out visitors too are marked access: 'anon'.
Two-way sync that merges per column
Section titled “Two-way sync that merges per column”Offline writes queue per transaction and upload only the columns that changed, so two people editing different fields of a row both keep their edit - see Conflicts for the full model. The limit is in the same breath: two edits to the same column resolve last write wins, a delete beats a concurrent edit, and this is not a CRDT - there is no text merging.
Three lines to adopt
Section titled “Three lines to adopt”A PGlite extension plus one sync() call - no codegen, no schema DSL, no client-side query
language to learn:
await pg.supapower.sync({ supabase, tables: ['items', { table: 'notes', cursor: 'updated_at' }],});See supapower.sync for every option.
Small, permissive, batteries included
Section titled “Small, permissive, batteries included”Apache-2.0 with zero runtime dependencies, plus a SharedWorker multi-tab host
(@supapower/worker), React (@supapower/react) and Vue
(@supapower/vue) bindings, and a typed
status and event API.
What it costs you
Section titled “What it costs you”- Supabase only, by design. Nothing here works against a plain Postgres.
- Sync granularity is the table, filtered by RLS and optionally by a per-table
filterwhose conditions areANDed columns - there is still no per-query shape or stream, noORand nothing that reaches across tables, so a client that may see a million rows of one slice will eventually hold them all. - Without a
cursorcolumn every start downloads each table whole; with one, the download is incremental but hardDELETEs never reach clients that were offline - see database migrations for the soft-delete convention. - The realtime path is Supabase Realtime’s
postgres_changes, so its delivery and scaling characteristics are yours too. - Single-column primary keys only; compound primary keys are not implemented.
- Unsynced local writes to
authenticatedtables are dropped on sign-out. - Conflict resolution is per-column last-write-wins, not a CRDT: no text merging.
- Pre-1.0 (
0.3.0). Both directions work; the public API may still change. No commercial support.
When something else fits better
Section titled “When something else fits better”- A document store rather than SQL, React Native, or one client syncing several kinds of backend: RxDB and its Supabase replication plugin, which reaches Supabase the same direct way this does.
- A backend that is not Supabase - a self-hosted Postgres, MongoDB, MySQL - or native mobile SDKs (Flutter, Kotlin, Swift): PowerSync.
- Read-path sync at CDN scale, or a Postgres that is not Supabase, with a write path you design yourself: Electric.
- Per-query partial sync of a large dataset with server-authoritative mutators: Zero.
- A managed service with an SLA and paid support: PowerSync Cloud or Electric Cloud.