Sync Engines for Postgres: An Opinionated Survey
Electric SQL, PowerSync, and Zero — how each one thinks about sync, and what that means for your architecture
Learning Objectives
By the end of this module you will be able to:
- Describe the core sync primitive of each major engine: shapes (Electric SQL), buckets (PowerSync), and queries (Zero).
- Contrast how each engine connects to and replicates from Postgres.
- Evaluate production maturity and stability signals for each engine as of early 2026.
- Distinguish read-only sync (Electric SQL's model) from read-write sync models.
Core Concepts
Three sync engines have emerged as the primary options for Postgres-backed local-first applications: Electric SQL, PowerSync, and Zero. Each one makes a fundamentally different design decision about what the core primitive of sync should be. That decision cascades into everything else: how authorization works, how you design your schema, how you integrate with Vue, and what your write path looks like.
Understanding the sync primitive is the load-bearing concept. Everything else is downstream of it.
How all three engines connect to Postgres
All three engines use PostgreSQL's logical replication to consume a continuous stream of changes from Postgres. Rather than polling the database, they subscribe to the Write-Ahead Log (WAL), which records every insert, update, and delete as it happens.
This is worth noting because it means the initial sync — seeding a client with a full snapshot of its relevant data — is a different operation from streaming ongoing changes, and it carries a storage cost. PostgreSQL 16 introduced binary format synchronization for initial data replication, significantly reducing transfer time and disk I/O overhead compared to the previous text format. This matters in practice: large initial snapshots used to risk exhausting WAL storage on the source server. If you are running Postgres 15 or earlier, this is a real operational concern worth planning for.
The WAL-based approach means all three engines get low-latency change propagation for free — changes are streamed as they are committed, not polled on an interval. The meaningful differences between the engines lie in what they sync and how they decide to whom.
Electric SQL — Shape-based sync
Electric SQL is built around a concept called Shapes. A Shape is the core primitive for controlling what data gets synced to a given client. Instead of syncing an entire table, you define a Shape that describes a filtered subset of rows — for example, all tasks belonging to a specific project, or all messages in a conversation the user is part of.
Shapes enable partial replication: each client subscribes to one or more Shapes and only receives the data those Shapes describe. This keeps bandwidth and local storage proportional to what a particular client actually needs.
The write path is a separate concern. Electric SQL implements a read-path sync model: the sync engine handles streaming reads from Postgres to clients, but writes are not managed by the sync engine. To write data, you send requests to a standard API layer — REST, GraphQL, tRPC, server actions — which writes to Postgres directly. Those changes then flow back to all subscribed clients through the read path.
Electric SQL draws a clean line: sync owns the read path, your API layer owns the write path.
This is a deliberate architectural choice. It keeps business logic for mutations on the server, which is familiar territory for anyone used to building conventional Nuxt or Next.js applications. The tradeoff is that you do not get optimistic writes managed by the sync engine out of the box — you handle that yourself, or you accept a round-trip on writes.
Electric SQL 1.0 reached General Availability in March 2025, with stable APIs described as suitable for mission-critical production use. It entered beta in December 2024 with production users already running on it.
PowerSync — Bucket-based sync
PowerSync organizes sync around buckets. A bucket is a named container of data; Sync Rules define which rows from Postgres belong in which buckets for which users.
Sync Rules are server-side YAML configuration. They implement a two-stage pipeline: the first stage (a parameter query) reads user-specific parameters from the JWT authentication token; the second stage (a data query) uses those parameters to filter which rows are included in the bucket for that user. JWT payload fields are accessible inside Sync Rules via functions like request.jwt() and request.user_id(), making role-based and attribute-based access control a first-class feature of the sync configuration. Crucially, Sync Rules take effect immediately without requiring an application redeploy — you can update access control policies without a release cycle.
On the client side, PowerSync automatically syncs Postgres with an embedded SQLite database on each device. The client queries that local SQLite database directly using standard SQL. Writes go back to Postgres through your own API layer, similar to Electric SQL's model, but PowerSync also provides an upload queue mechanism to handle offline write buffering.
PowerSync's maturity story is the strongest of the three. Its core sync technology has been in production for over a decade, used daily by tens of thousands of enterprise users at Fortune 500 companies in energy, manufacturing, and mining. The platform reports zero incidents of data loss since its original release, and is proven in remote and disconnected environments with large data volumes.
Sync Rules are PowerSync's mechanism for both data shaping and access control in a single place. Understanding them well is prerequisite to doing anything interesting with PowerSync — they will come up again in the authorization and schema design modules.
Zero — Query-driven sync
Zero, developed by Rocicorp, takes a fundamentally different approach. There are no shapes to define server-side, no bucket configurations to write. Instead, Zero uses a query-driven architecture: the client writes queries — using Zero's ZQL query language — that specify exactly what data it needs. Zero syncs only the data required to satisfy those active queries.
Under the hood, a stateful middleware service called zero-cache maintains a SQLite replica of the upstream Postgres database using logical replication. When a client submits a query, zero-cache resolves it against the local replica and streams only the relevant rows to the client.
Authorization in Zero is enforced at query time through a server-side transform endpoint. When a client executes a query, it is forwarded to the server for authorization checks before results are returned. Permissions are evaluated per query, not pre-computed in static sync rules. If a user loses access to a resource mid-session, the next query will be rejected at the server level, and already-synced stale data is eventually garbage-collected on reconnection.
Unlike Electric SQL and PowerSync, Zero handles both the read and write paths within its sync model, enabling optimistic mutations where writes are reflected locally before they are confirmed by the server.
Zero is compelling but not yet production-ready for most teams. Rocicorp is targeting a beta release in late 2025 or early 2026, where "beta" means ready for the average new rich web application. As of March 2026, Zero should be treated as a pre-beta project. Evaluate it, build prototypes — but plan your production architecture around Electric SQL or PowerSync.
Compare & Contrast
| Electric SQL | PowerSync | Zero | |
|---|---|---|---|
| Core sync primitive | Shape (filtered table subset) | Bucket (named row container) | Query (client-defined ZQL) |
| Postgres connection | Logical replication (WAL) | Logical replication (WAL) | Logical replication (WAL) via zero-cache |
| Client storage | In-memory / custom | Embedded SQLite | In-memory / zero-cache |
| Write path | Your API layer | Your API layer + upload queue | Integrated (optimistic writes) |
| Access control location | Shape definition + your API | Sync Rules (JWT-aware YAML) | Server-side query transform |
| Access control update | Requires shape redeploy | Immediate, no redeploy | Per-query, real-time |
| Production maturity | GA since March 2025 | Production-grade for 10+ years | Pre-beta as of March 2026 |
| Best fit | Streaming dashboards, read-heavy apps | Offline-first mobile/desktop, enterprise | Collaborative apps, advanced UX |
The sharpest architectural divide is read-only vs. read-write sync:
- Electric SQL explicitly separates sync (reads) from writes. Your application manages the write path independently.
- PowerSync similarly keeps writes in your hands, but provides tooling (upload queues, offline buffering) to manage the complexity.
- Zero unifies both paths inside the sync model, trading configurability for integration depth.
For teams already comfortable with a Nuxt API layer or server actions, the Electric SQL and PowerSync models will feel natural: you already know how to write to a database through an API endpoint. Zero's unified model is more ambitious and more opinionated — and requires waiting for it to reach stability.
Worked Example
Scenario: You are building a project management tool. Users belong to organizations. Each user should only see tasks that belong to their organization.
Here is how each engine approaches scoping that data:
Electric SQL
You define a Shape on the server that filters tasks by org_id. The client subscribes to that Shape, passing the user's org_id as a parameter. The Shape ensures the client only receives rows matching that organization.
GET /v1/shape?table=tasks&where=org_id='<org_id>'
Writes (creating a task) go to your Nuxt server action or API route, which inserts into Postgres. The insert flows back through the Shape to all subscribed clients in that org.
PowerSync
You write a Sync Rule in YAML. The parameter query reads user_id from the JWT. The data query joins on an org_memberships table to determine which org_id values the user has access to, then returns only tasks with matching org_id values.
bucket_definitions:
org_tasks:
parameters: SELECT org_id FROM org_memberships WHERE user_id = request.user_id()
data:
- SELECT * FROM tasks WHERE org_id = bucket.org_id
The client queries its local SQLite replica. Creating a task goes through your API layer; PowerSync's upload queue handles it if the user is offline.
Zero
The client writes a ZQL query requesting tasks where org_id matches the user's org. Zero's server-side transform validates that the requesting user actually belongs to that org before returning results. No sync rule configuration is written server-side; the query itself is the sync specification.
const tasks = useQuery(
z.query.tasks.where('org_id', orgId)
)The Zero example above illustrates the pattern; the exact API surface may change before stable release. Do not build production systems on it yet.
The three approaches encode the same access control logic in different places: in the Shape subscription parameters (Electric SQL), in server-side Sync Rules (PowerSync), or in a server-side query transform evaluated at runtime (Zero). Module 04 will go deeper into authorization patterns for each engine.
Key Takeaways
- All three engines read from Postgres via logical replication (WAL). They differ in what they sync and how they decide to whom — not in how they connect to the database.
- The sync primitive determines the architecture. Shapes (Electric SQL) are filtered table subscriptions defined at request time. Buckets (PowerSync) are named data containers defined by server-side Sync Rules. Queries (Zero) are client-defined and evaluated server-side at runtime.
- Electric SQL and PowerSync both have separate write paths. You write to Postgres through your own API layer; the sync engine handles reads only. PowerSync adds offline write buffering; Electric SQL leaves that to you.
- Production maturity varies significantly. Electric SQL reached GA in March 2025. PowerSync's core technology has been in production for over a decade. Zero is targeting beta in late 2025 / early 2026 — it is not production-ready as of March 2026.
- Access control lives in different places in each engine. PowerSync Sync Rules are the most explicit and portable; Electric SQL keeps access control at the Shape and API layer; Zero enforces it per-query at runtime via a transform endpoint. This is the most consequential architectural decision you will make when choosing an engine.
Further Exploration
Electric SQL
- Shapes — Electric SQL Guide — The official reference for Electric SQL's core sync primitive.
- Electric 1.0 Released — The GA announcement, useful for understanding what stabilized and what the roadmap looks like.
PowerSync
- Sync Rules — PowerSync Docs — Full reference for PowerSync's two-stage ETL sync configuration.
- Postgres Logical Replication Challenges & Solutions — PowerSync Blog — Practical coverage of WAL-related operational concerns including initial sync storage risks.
Zero
- What is Sync? — Zero Docs
- Zero Roadmap — Track Zero's path to beta and production readiness.
- Testing Zero: Rocicorp's Ultra-Fast Sync Engine — Marmelab — An independent hands-on evaluation with honest coverage of current limitations.
Comparative Analysis
- Sync Engines Compared: Electric SQL vs Convex vs Zero — MerginIT — A broader comparison useful for understanding where these engines sit in the wider sync landscape.