sqlite-sync: CRDT offline-first sync for SQLite, with a real backend requirement
CRDT-based offline-first sync for SQLite. Syncs automatically with SQLite Cloud, PostgreSQL, and Supabase. No conflicts, no data loss, no backend to build. For offline-first apps and AI agents.
At a glance
- What is it?
- sqlite-sync turns a local SQLite database into a conflict-free replica that syncs with SQLite Cloud, PostgreSQL or Supabase. The CRDT layer is genuinely interesting; the deployment story is where you need to read carefully.
- Who is it for?
- Adopt sqlite-sync if you already run PostgreSQL or self-hosted Supabase and need offline writes from mobile, desktop or edge clients that merge without manual conflict handling, and if you can accept a C extension loaded into every client. Do not adopt it if you wanted peer-to-peer sync with no server at all, or if you need a documented rollback path before you commit.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 5 days ago.
- What is it written in?
- Mainly C, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 25, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem sqlite-sync targets: two writers, one SQLite file, no coordinator
SQLite is the obvious database for a mobile app, a desktop tool, an IoT device or an agent process, because the database is a file next to the code. The moment two of those devices touch the same logical dataset, the file model stops helping. You either write a sync protocol, or you give up local writes and call a server.
sqlite-sync is aimed at the first option. Its README describes it as "a multi-platform extension that turns any SQLite database into a conflict-free, offline-first replica" that syncs with SQLite Cloud nodes, PostgreSQL servers, or Supabase instances. The intended audience is stated directly: offline-first apps on mobile, desktop, IoT and edge, plus AI agents that keep memory, notes or shared state in SQLite.
The design claim is that devices update independently, even offline, and all changes merge automatically. There is no manual conflict resolution step in the workflow the README describes. That is a narrower and more useful promise than "sync", because it tells you what happens when two replicas disagree: the merge is deterministic and handled by the CRDT layer, not by your application code.
How the CRDT layer and Block-Level LWW actually merge rows
The extension implements several CRDT types, listed in the README's feature table as Causal-Length Set, Delete-Wins, Add-Wins, and Grow-Only Set. Each is a different rule for combining concurrent operations, and the choice of rule is what makes the merge deterministic without a coordinator.
The second mechanism is Block-Level LWW, described as line-level merge for text and markdown columns, where concurrent edits to different lines are preserved. Last-write-wins at row granularity would discard one of two edits to the same document; line-level merge keeps both when the edits land on different lines. The README says this was designed specifically for markdown files edited by multiple agents working on different sections of the same document. That is a concrete design decision, and it is the part of the project that is hardest to replicate with a hand-rolled sync layer.
Delivery is handled by what the README calls CloudSync microservices, a network of services that route, package and deliver changes between SQLite and other DBMS nodes. The extension embeds its own network layer, built on libcurl or a platform-native path. The Makefile confirms both paths exist: it sets NATIVE_NETWORK := ON for Mac Catalyst, and otherwise links with -lcurl. So the binary you build has a networking dependency you should know about before shipping it to a constrained target.
Installing sqlite-sync and syncing your first table
The README says to download a pre-built binary from the Releases page, or install a platform package. For the SQLite CLI and C, the extension is loaded from a local file. The README gives this form:
.load ./cloudsyncIt also gives the programmatic equivalent, SELECT load_extension('./cloudsync'). After loading, you create a normal table and call cloudsync_init on it. The README's own example is a tasks table:
CREATE TABLE tasks (
id TEXT PRIMARY KEY,
title TEXT NOT NULL DEFAULT '',
done INTEGER NOT NULL DEFAULT 0
);
SELECT cloudsync_init('tasks');What you should see is that cloudsync_init returns without error and the table is now tracked. The README stops its quick start at that point, so the connection details for the backend are not shown in the README itself; the PostgreSQL and Supabase quickstarts live under docs/postgresql/quickstarts/ and are the place to look for them.
Package installation differs by platform. Android uses a Gradle dependency, ai.sqlite:sync:1.0.0, from Maven Central. Flutter uses flutter pub add sqlite_sync. Expo and React Native use npm packages @sqliteai/sqlite-sync-expo and @sqliteai/sqlite-sync-react-native. Swift is not a package manager one-liner: the README says to add the repo as a Swift Package dependency, follow steps 4 and 5 of the sqlite-extensions-guide for iOS, and load the extension using CloudSync.path.
Building from source is a Makefile job. The Makefile pins CURL_VERSION ?= 8.12.1 and OPENSSL_VERSION ?= openssl-3.6.0, and it lets you point at a specific sqlite3 binary for tests, for example make test SQLITE3=/opt/homebrew/Cellar/sqlite/3.50.4/bin/sqlite3. Note that the default build pulls and builds curl and OpenSSL, which is not a small dependency footprint for an extension.
Row-level security is server-enforced, which changes your threat model
The feature table lists Row-Level Security with the qualifier "Server-enforced: each client syncs only the rows it is authorized to see." That phrasing matters. Filtering happens on the server side, not in the client extension, so the client is not the component that decides what a device is allowed to pull down.
The README applies this to multi-tenant scenarios: CRM systems syncing leads and clients per user, and SaaS platforms giving row-level access per user or team inside a single shared database. If you are building a per-user dataset over one Postgres instance, that is the shape the project expects.
The consequence for anyone auditing the design is that your authorization rules live with the backend, whether that is SQLite Cloud, your own PostgreSQL, or a self-hosted Supabase. The extension does not replace that layer, and the README does not present it as doing so.
Where sqlite-sync is the wrong tool
The README's headline claim is "no extra infrastructure", qualified by the sentence that a globally distributed network of CloudSync microservices handles routing and delivery. Read together, those two statements mean something specific: you do not build a sync server, but you do need a backend the extension can talk to. The README's own callout asks "Need a sync backend?" and points at any PostgreSQL or self-hosted Supabase instance, or at SQLite Cloud CloudSync. If your requirement is genuinely serverless, two SQLite files merging directly with each other, this is not that project.
The second limitation is documentation depth. The README's quick start ends mid-sentence at "### 3. Use your", and the connection configuration for a backend is not shown in the README itself. Anyone evaluating this needs to read docs/postgresql/quickstarts/postgres.md and the installation guide before concluding the setup is a single function call.
Third, this is a C extension loaded into the SQLite process. On platforms where you cannot load extensions, or where your SQLite build is locked down, the extension model is a blocker regardless of how good the merge algorithm is. The Makefile's NATIVE_NETWORK and libcurl branches also mean you should check which networking path your target platform ends up using.
Finally, the repository's LICENSE.md is present, but the project metadata reports the licence as NOASSERTION, meaning no standard SPDX identifier was detected. Treat the licence terms as something to read directly rather than something to infer.
sqlite-sync compared with sqlite-rsync and hand-rolled sync
The most direct alternative in the search data is sqlite-rsync, which is a file-level tool: it moves the database file (or its pages) between machines. That approach is simple and has no schema awareness, but it cannot merge two divergent writers. If both devices wrote while offline, file transfer gives you two files and a decision, not a merged database. sqlite-sync's CRDT layer exists precisely to avoid that decision.
A second alternative is writing your own sync: a changes table, a push/pull endpoint, and application-level conflict rules. That gives you full control over the wire format and no extension dependency, and it is the right answer if your conflict rules are domain-specific in a way no generic CRDT captures. The cost is that you own the merge logic forever, including the cases where concurrent edits to the same markdown document need line-level resolution. Block-Level LWW is the part of sqlite-sync that is genuinely hard to reimplement well.
The third comparison is against server-authoritative sync, where the client sends writes and the server is the only source of truth. That model is simpler to reason about and easier to audit, but it requires connectivity for writes. sqlite-sync's whole premise is that writes happen locally first, which is a different product decision, not a better one.
Maintenance, upgrade cost and licence questions to settle first
The most recent push to the repository was on 2026-09-07, and the latest release listed is 1.1.2 from 2026-07-14, following 1.1.1 on 2026-07-13 and 1.1.0 on 2026-07-09. That is a tight release cadence in July with a push in September. The repository is not archived.
Upgrade cost is concentrated in the extension binary rather than in your schema. Because sync is enabled per table with cloudsync_init, the surface you must retest after an upgrade is the set of synced tables and the merge behaviour on them, not the whole application. The CHANGELOG.md at the repository root is where version-to-version changes are recorded; the release list alone does not say what changed between 1.1.1 and 1.1.2.
The dependency chain is the other recurring cost. The Makefile pins CURL_VERSION ?= 8.12.1 and OPENSSL_VERSION ?= openssl-3.6.0, and a default build downloads and compiles both. If you build from source, curl and OpenSSL updates land in your build process, not in someone else's. If you consume a pre-built binary from Releases, that responsibility sits with whoever publishes it.
On licensing: the repository contains LICENSE.md, but the detected licence is NOASSERTION. That is not a legal conclusion, and it is not advice. It is a signal that you should open LICENSE.md and read it against how you intend to distribute the extension, particularly on iOS and Android where the binary ships inside your app.
Editorial conclusion
Adopt sqlite-sync if you already run PostgreSQL or self-hosted Supabase and need offline writes from mobile, desktop or edge clients that merge without manual conflict handling, and if you can accept a C extension loaded into every client. Do not adopt it if you wanted peer-to-peer sync with no server at all, or if you need a documented rollback path before you commit. Verify first that the release binary for your platform loads with .load ./cloudsync, that cloudsync_init succeeds on one table, and that you can reach your chosen backend from the client. The repository's own README points at a managed instance and at PostgreSQL quickstarts rather than at a standalone sync server.
Frequently asked questions
What is sqlite-sync and who is it for?
It is a multi-platform SQLite extension that turns a local database into an offline-first replica using CRDTs. The README targets offline-first apps on mobile, desktop, IoT and edge, plus AI agents that keep memory or shared state in SQLite.
How do I install sqlite-sync for the SQLite CLI?
The README says to download a pre-built binary from the Releases page, or install a platform package. For the SQLite CLI and C it gives .load ./cloudsync, and the programmatic equivalent SELECT load_extension('./cloudsync').
Does sqlite-sync sync to PostgreSQL or Supabase?
Yes. The README states it syncs with SQLite Cloud nodes, PostgreSQL servers and Supabase instances, and links to quickstarts under docs/postgresql/quickstarts/ for Postgres and self-hosted Supabase.
Can sqlite-sync run without any backend server?
No. The README says a network of CloudSync microservices handles routing and delivery, and its callout points at a PostgreSQL instance, a self-hosted Supabase instance, or SQLite Cloud CloudSync as the sync backend.
Is sqlite-sync available for Android and Flutter?
The README lists an Android Gradle dependency, ai.sqlite:sync:1.0.0, on Maven Central, and a Flutter package installed with flutter pub add sqlite_sync from pub.dev.
Why does sqlite-sync have no SPDX licence identifier?
The project metadata reports the licence as NOASSERTION, meaning no standard identifier was detected. The repository does contain a LICENSE.md, so the terms should be read there directly.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/sqliteai-sqlite-sync)