DATE:
AUTHOR:
PowerSync Product Team
PowerSync Service Convex

Announcing Convex Backend Support (Experimental)

DATE:
AUTHOR: PowerSync Product Team

Convex can now be used as a source for PowerSync, available in an experimental release. It's intended for early testing against your use cases and overall feasibility. Your feedback will shape the direction of this integration 🫡

Why Convex

Over the past few months, we've seen a steady uptick in community requests to support Convex. If you haven't yet used it: Convex is a backend platform where you define your schema and write your business logic as TypeScript functions that run against a reactive database. Mutations are ACID transactions, queries are reactive so subscribed clients re-render when their data changes, and the runtime is open source and self-hostable.

Adding PowerSync to a Convex app gives you offline-first behavior, optimistic updates, and partial sync. Your app reads and writes against a local SQLite database, so it stays responsive when the network is slow or absent. Writes appear instantly because they apply to the local database first, then upload to Convex in the background. Each client only syncs the subset of Convex data it needs, so you don't sync your whole database to every device. Your existing Convex mutations still handle writes to the backend.

Architecture

PowerSync has two main pieces: the PowerSync Service that connects to your source database (Convex in this case), and a client SDK that maintains a local SQLite database on each device. The PowerSync Service watches Convex and streams the rows each client should see, based on the Sync Streams you define. The client SDK exposes that data to your app as local SQLite, and queues client writes for upload back to your Convex mutator functions.

The PowerSync Service is built so each source database plugs in as its own module on top of a shared core. The Convex module replicates from Convex's Streaming Export API, maps Convex types to SQLite types, and manages replication checkpoints. Convex being open source helped, since the Streaming Export API is was well-documented and easy to build against.

Initial replication takes a single Convex snapshot of every selected table at the same consistent point. After that, streaming replication polls the global document_deltas endpoint and filters rows based on your Sync Streams.

Convex mutations are ACID transactions, and all writes within a single mutation share a commit timestamp. PowerSync replicates them together as one atomic batch, so clients never see a partial result from a single mutation.

PowerSync clients authenticate to the Service using JWTs. If your app uses Convex Auth, you can pass Convex Auth tokens directly as the JWT. You don't need to run a separate auth service for sync.

How to get started

1. Prepare your Convex deployment

PowerSync works with both Convex Cloud and self-hosted Convex deployments. On the Convex side you need to:

  1. Add a powersync_checkpoints table to your Convex schema. This is required because PowerSync guarantees causal consistency that spans SQLite and Convex.

  2. Deploy a powersync_checkpoints:createCheckpoint mutation that PowerSync calls after recording a write checkpoint.

  3. Generate a Convex deploy key for the deployment you want to replicate.

The exact schema and mutation snippets are in the Convex Source Database Setup docs.

2. Connect Convex to PowerSync

PowerSync Cloud: add your Convex deployment URL and deploy key in the PowerSync Dashboard. See Convex specifics.

Self-hosted PowerSync: add a convex connection to your service.yaml. See Self-Hosted Instance Configuration.

Then define Sync Streams to control which Convex tables and rows sync to each client.

3. Set up the client

Follow the Setup Guide for client SDK installation, defining your local schema, and wiring up the upload path. For Convex, your upload path calls your existing Convex mutations directly.

Demo app

For an end-to-end working example, see the PowerSync + Convex To-Do List Demo. It's a React app that uses Convex Auth and calls Convex mutations directly, so no separate backend application is required.

The data flow: mutations start as local SQLite writes in the client. PowerSync queues those writes, the connector uploads them to Convex mutations, Convex updates the backend tables, and PowerSync streams the resulting changes back down to subscribed clients.

Known limitations and caveats

  1. Schema changes: Convex field additions, removals, and type changes flow through as normal document mutations and don't require a re-snapshot. Deleting a table from the Convex Dashboard, however, doesn't emit per-document delete events in document_deltas, so previously replicated rows can stay on clients. Use the dashboard's "Clear Table" action before deleting a table, or delete documents through mutations. See Convex schema change handling for the full details.

  2. ID mapping: PowerSync clients generate UUIDs locally before writes are uploaded, so your Convex mutations need to map between client UUIDs and Convex _id values. This follows the Sequential ID Mapping pattern used with our other backends.

  3. Type mapping: Convex Int64 values sync as base-10 text and Bytes values as base64 text. To sync a Convex Int64 as a SQLite integer, cast it explicitly in your Sync Streams with CAST(value AS INTEGER).

  4. Latency: PowerSync polls the Convex document_deltas endpoint every 1000ms. This interval isn't currently configurable on PowerSync Cloud. On self-hosted PowerSync you can lower it via the polling_interval_ms connection parameter for reduced latency, at the cost of higher request load on Convex.

  5. Replication metrics: ROWS_REPLICATED and TRANSACTIONS_REPLICATED are reported. DATA_REPLICATED_BYTES and CHUNKS_REPLICATED aren't implemented for Convex yet.

Feedback and help

The Convex connector is released as an experimental feature. APIs and behavior may still change, and we can't yet guarantee continued support or long-term stability. This release is intended for early testing and to invite feedback. Your feedback will directly influence whether, and how, this integration evolves.

Try it, and tell us what works for you and what doesn't. Please open an issue on GitHub or join us in Discord to share your feedback or get help.

Powered by LaunchNotes