Comparison
All five solve the same problem - read and write locally, converge later - and differ in what you have to run, what the client stores, and who decides what a client may see. One note before the table: Supapower builds on PGlite, which is an Electric SQL project, but does not use Electric’s sync service - the two are unrelated beyond sharing that dependency.
| Supapower | RxDB + Supabase plugin | PowerSync | Electric | Zero | |
|---|---|---|---|---|---|
| Extra service to run | None - Supabase’s Data API and Realtime | None - the same Data API and Realtime | PowerSync Service (cloud or self-hosted) | Electric sync service (cloud or self-hosted) | zero-cache server (cloud or self-hosted) |
| Access to your database | None beyond the Supabase client (HTTPS + websocket) | None beyond the Supabase client (HTTPS + websocket) | Logical replication connection to your database | Logical replication connection to your Postgres | Logical replication connection to your Postgres |
| Local store | PGlite - Postgres compiled to WASM | Pluggable; localStorage and Dexie free, IndexedDB/OPFS/SQLite paid | SQLite | Your choice; PGlite, TanStack DB or plain JS state | Its own client store |
| Local queries | SQL (PostgreSQL) | Mango-style selectors over JSON documents | SQL (SQLite) | Whatever your client store offers | ZQL |
| Backends supported | Supabase only | Supabase, CouchDB, GraphQL, Firestore or any HTTP backend | Postgres, MongoDB, MySQL, SQL Server and more | Any Postgres with logical replication | Any Postgres with logical replication |
| Direction | Two-way | Two-way | Two-way | Read path only - you build the write path | Two-way |
| What a client may sync | Your RLS policies, plus an optional per-table filter | Your RLS policies, plus a client-side pull query | Sync Streams, configured separately from RLS | Shapes, scoped by your auth proxy | ZQL queries plus permission rules in app code |
| Write path | Queued locally, upserted through PostgREST | Queued locally, pushed through PostgREST with concurrency guards | Your own upload endpoint (uploadData) |
Your own API | Custom mutators on the server |
| Conflict model | Per-column merge in Postgres, last-write-wins within a column | Per document; the server version wins unless you write a handler | Server-authoritative, last-write-wins per field | Yours to define | Server-authoritative; optimistic client state is rolled back |
| Partial sync of a big table | Yes - a per-table filter callback, resolved per session |
Yes - pull query filters | Yes - Sync Streams | Yes - Shapes | Yes - queries |
| Demands on your schema | A single-column primary key; updated_at to sync incrementally |
String primary keys, plus _deleted and _modified columns |
Client-side schema in app code | None | None |
| Client platforms | Web, Node, Bun, Deno (JS/TS) | Web, Node, React Native, Electron, Capacitor | Web, React Native, Flutter, Kotlin, Swift | Web and anything that can speak its HTTP API | Web |
| License | Apache-2.0 | Apache-2.0 core; paid license for the faster storages | SDKs Apache-2.0; service under FSL 1.1 with an Apache-2.0 future license | Apache-2.0 | Apache-2.0 |
| Cost model | Only what Supabase already costs you | Supabase, plus a per-year license if you need a premium storage | Free tier, then metered usage | Free self-hosted; metered cloud | Free self-hosted; per-instance cloud |
| Maturity | Pre-1.0, no commercial support | Production, paid support available | Production, commercial support | Production, commercial support | 1.0, commercial support |
Reading the table
Section titled “Reading the table”RxDB with its Supabase replication plugin is the
closest architectural match, and the reason the first two rows of the table look identical: it also
talks straight to Supabase over PostgREST and Realtime, so “nothing to deploy” is not unique to
Supapower. What differs is the local database and the conflict model. RxDB is a document store
queried with Mango-style selectors - no SQL, no joins, no Postgres - and conflicts are resolved per
document, where the default handler
drops the local version in favor of the server’s and merging two edits means writing a handler
yourself. It also asks your Supabase tables for string primary keys and _deleted / _modified
columns. Choose it for a document model, React Native, or one client syncing several kinds of
backend. Choose Supapower to keep writing SQL against a real Postgres and to get column-level
merging without writing the merge.
PowerSync is the closest relative in intent and Supapower’s stated inspiration - it’s also Supabase’s own listed offline-first integration. Choose it for non-Supabase or non-Postgres backends, native mobile SDKs, Sync Streams that carve a large table into per-user buckets, or paid support and uptime SLAs. Supapower’s trade: no service to run, no replication slot on your database, and RLS instead of a second rules language to maintain.
Electric is a read-path sync engine: Shapes delivered over HTTP and cached by a CDN, deliberately leaving writes to the application to design. Choose it for large read-heavy fan-out, or a Postgres that is not Supabase. Supapower ships the write path instead - the queue, the retries, the per-column conflict rules - so there’s nothing left to build.
Zero syncs whatever a query asks for and applies writes through server-authoritative mutators - closest to “your backend, but instant”. Choose it when authorization and mutation logic belong in server code and the working set is a query, not a table. Supapower’s authorization is the database’s own: no second permission model to write.
Partial sync in detail
Section titled “Partial sync in detail”In every case it does come down to a filter attached to a table - what differs is who writes the filter, and whether a client can change it at runtime.
- Supapower - a
filtercallback on the table config, composed in client code, is handed the current session so it can key off a claim (e.g.filter.eq('workspace_id', session.user.app_metadata.workspace_id)), and is applied to both the PostgREST download and thepostgres_changessubscription. Conditions areANDed, with noOR, no subqueries and no joins, so a membership rule only fits as aninlist of at most 100 values. RLS is still what stops a client widening its own filter. Seefilter. - RxDB - the Supabase plugin takes a
pull.queryBuilder, so the pull is an ordinary PostgREST query you can narrow (query.eq('status', 'active')) or point at joined tables. The filter lives in client code, so RLS is still what stops a client from asking for more. See Supabase replication. - PowerSync - a stream is a SQL-like query in a YAML config deployed to the service, e.g.
SELECT * FROM lists WHERE owner_id = auth.user_id(). Filters take parameters from the token, the connection or the client’s own subscription, and each distinct parameter value becomes a bucket that syncs as a unit. See Sync Streams. - Electric - a shape is one table plus an optional
whereclause andcolumnslist, requested over HTTP; subqueries cover membership and sharing rules (id IN (SELECT user_id FROM memberships WHERE org_id = $1)). Shape definitions are immutable, and production apps are expected to have their backend issue them so a client cannot widen its own filter. See Shapes. - Zero - filters are ordinary queries in application code.
zero-cacheasks your server to resolve each query by name, which is where it can be narrowed further for permissions, and a query can pull in relationships, so the unit is a query result rather than a single table. See Queries.
The filter is what narrows both the download and the subscription;
cursor still only narrows how far back in time
a download reads, not which rows qualify. RLS still decides what the filter is allowed to return -
a filter callback can only narrow further, never widen past it. A filter resolved from the
session is re-resolved on every auth change, and a change to it empties the local table and
downloads it again.
When a table is bigger than the narrowest slice a column filter can express - a membership rule that needs a join, say - splitting the hot subset into its own table - optionally in a separate schema - is the answer for that slice.
Also in the field
Section titled “Also in the field”- WatermelonDB - a SQLite-backed reactive database for React and React Native; you implement the sync backend yourself against its pull/push protocol.
- LiveStore - an event-sourced SQLite store with pluggable sync backends (Cloudflare Durable Objects first-party).
- Triplit - a full-stack syncing database with CRDT-based conflict
resolution, requiring its own server (
@triplit/server). - Verdant - an IndexedDB store plus a small Node and SQLite sync server of its own, MIT-licensed and openly experimental; it replaces the backend rather than syncing an existing Supabase one, which is why it is not in the table above.
Checked against the linked docs in September 2026. Sync engines move fast - follow the link before betting on a row.