sqlite-sync: CRDT Replication as a SQLite Extension
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 file into an offline-first replica that merges changes through CRDTs and ships them over an embedded network layer. The extension is small to adopt, but the merge model and the backend it talks to are the parts that decide whether it fits.
- Who is it for?
- Adopt sqlite-sync if your application already keeps its state in SQLite and you need independent writes from several devices or agents to converge without a hand-written merge layer. Do not adopt it if you need a documented CRDT type per column, if your schema depends on foreign key enforcement across synced tables, or if your data lives in a database engine other than SQLite.
- 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 4 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 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The problem sqlite-sync targets: independent writes that must converge
Two devices editing the same row while offline is the ordinary case for mobile, desktop, edge, and agent workloads. The usual answers are last-write-wins at the row level, which silently drops one edit, or a server that serialises writes, which requires connectivity and a backend you have to build and operate. sqlite-sync takes a third route: it treats the SQLite database as a replica and uses CRDTs to merge changes so that concurrent writes produce a deterministic result. The README frames the guarantee as no data loss, no conflicts, and no extra infrastructure, with a network of CloudSync microservices handling routing and delivery.
The audience is stated plainly. Offline-first apps on mobile, desktop, IoT, and edge keep a local SQLite database and sync when connectivity returns. AI agents that hold memory, notes, or shared state in SQLite get a way to share that state across instances without a coordinator. The second case is the more distinctive one: the README says Block-Level LWW was designed specifically to keep markdown files in sync, so that agents editing different sections of the same document preserve all changes. That is a narrower and more interesting claim than generic offline sync.
What the extension actually does to your database
The mechanism visible in the README is a SQLite loadable extension. You load it with `.load ./cloudsync` in the CLI or `SELECT load_extension('./cloudsync');` in C, then call `SELECT cloudsync_init('tasks');` on a table to enable sync for it. The README's quick start creates a `tasks` table with a TEXT primary key and two columns, then calls `cloudsync_init` on it. Nothing in the supplied material describes what `cloudsync_init` writes into the schema, whether it adds shadow tables, triggers, or metadata columns, or how it behaves if called twice. That is a gap worth noting: the call is the whole adoption surface, and its side effects on the schema are not documented in the material available here.
The merge algorithms named are Causal-Length Set, Delete-Wins, Add-Wins, and Grow-Only Set. These are set-oriented CRDT types, which fits a model where rows and their membership in a table are the replicated unit. Block-Level LWW operates at a finer granularity: the README describes it as line-level merge for text and markdown columns, where concurrent edits to different lines are preserved. Read together, the design suggests row-level convergence through the set CRDTs plus line-level convergence inside text columns through LWW. What the material does not say is how a column is assigned a CRDT type, whether that is automatic or configured, or what happens when two writers edit the same line rather than different lines. LWW implies one wins, but which one is not stated.
Networking is embedded. The README lists libcurl or native networking as part of the extension, and describes syncing as a single function call, though the quick start is truncated before that call appears. Row-level security is server-enforced, with each client syncing only the rows it is authorised to see. That is a server-side property, so it depends on the backend you point the client at, not on the extension alone.
Getting it running: install paths and the one call that matters
Installation is per platform. For SQLite CLI and C, the README gives `.load ./cloudsync` or `SELECT load_extension('./cloudsync');`. Swift is added as a Swift Package dependency with the extension loaded through `CloudSync.path`. Android uses `implementation 'ai.sqlite:sync:1.0.0'` from Maven Central. Flutter uses `flutter pub add sqlite_sync`. Expo and React Native use `npm install @sqliteai/sqlite-sync-expo` and `npm install @sqliteai/sqlite-sync-react-native` respectively. WASM is published as `@sqliteai/sqlite-wasm` on npm. Pre-built binaries are on the Releases page, and the repository points to a fuller installation guide for platform-specific code examples.
The setup sequence in the README is three steps: load the extension, create a table, call `cloudsync_init`. The example table is deliberately plain, with a TEXT primary key and no foreign keys. If your schema has constraints, generated columns, or triggers, the material does not describe how they interact with the sync layer. The backend choice is separate: you point the extension at SQLite Cloud, a PostgreSQL instance, or a self-hosted Supabase instance, and the README links separate quickstarts for the PostgreSQL and Supabase paths. The managed option is SQLite Cloud CloudSync, which has a pricing page and a free managed instance sign-up. There is also a live demo linked from the header.
The CRDT model is the main thing to interrogate before adopting
CRDTs buy convergence, and they charge for it in two currencies: metadata and semantics. The README does not describe the metadata overhead per row or per column, so the storage and wire cost of enabling sync on a large table is unknown from this material. The semantic cost is more visible. Four set CRDT types are listed by name but not mapped to situations. A developer reading this cannot tell from the README whether a given table will behave as Add-Wins or Delete-Wins, or whether that is chosen at `cloudsync_init` time or inferred. Those two policies give opposite answers when one client deletes a row while another adds a reference to it. That is not a detail you want to discover in production.
Block-Level LWW has the same shape of question at a smaller scale. If two agents edit the same line of a markdown document, LWW picks a winner and the other edit is gone. The README's claim is carefully scoped to different lines, and it is honest about that scope. The practical consequence is that this is not a collaborative text editor in the operational-transform sense; it is a document store where non-overlapping edits survive. For agent memory and note-taking that is often enough. For two people typing in the same paragraph it is not, and the README does not claim otherwise.
Where sqlite-sync is the wrong tool
The first boundary is the database engine. sqlite-sync is a SQLite extension. It syncs to PostgreSQL and Supabase, but the local replica is SQLite. If your application already runs on PostgreSQL everywhere, adding a SQLite replica on each client to reach a PostgreSQL server is a large architectural change, not a small one.
The second boundary is schema complexity. The README's examples use flat tables with a TEXT primary key. Nothing in the supplied material covers foreign keys, cascading deletes, or cross-table invariants. CRDT merge operates per replicated unit, so an invariant that spans two tables cannot be enforced by the merge algorithm alone; it would have to hold by construction or be repaired after sync. If your data model depends on referential integrity, verify how the extension handles it before you design around it.
The third boundary is the backend dependency. The README says there is no backend to build, and that is true in the sense that you do not write the sync protocol. It is not true in the sense that you run nothing. You still need a SQLite Cloud account, a PostgreSQL server, or a Supabase instance, and the CloudSync microservices are part of the delivery path. Self-hosting the PostgreSQL or Supabase side is documented, but the routing layer's operational characteristics are not described in this material.
Alternatives and how the approach differs
The closest comparison is SQLite's own session extension, which records changesets from a session and lets you apply them to another database. The difference in approach is fundamental. Session changesets are a change-capture and replay mechanism; applying two divergent changesets to the same row requires you to decide what happens, and SQLite's conflict handling is a callback you write. sqlite-sync moves that decision into the data type: the merge is deterministic and happens without a callback. The trade is that you give up control over the merge policy in exchange for not having to write one. If your conflict rules are domain-specific, for example a quantity that should be summed rather than picked, a changeset approach with explicit resolution may fit better than a fixed CRDT type.
The other comparison worth making is against server-authoritative sync, where clients push writes to a server that serialises them and pulls the result. That model is simpler to reason about and gives you a single source of truth for validation and access control. It fails when clients are offline for long periods or when you do not want a server in the write path at all. sqlite-sync's row-level security is server-enforced, so it keeps part of that server-authoritative property while letting writes happen locally first. Whether that combination behaves the way you expect depends on the backend, and the README does not spell out the interaction between local writes and server-side authorisation.
Licence, releases, and what maintenance looks like
The repository metadata reports the licence as NOASSERTION, which means no standard licence identifier was detected. The README does not state a licence either. Before you ship anything that links this extension, check the licence file or the release artifact directly rather than assuming. This is a factual gap, not a legal opinion, and it is the kind of gap that blocks adoption in organisations with licence review processes.
On releases, the recent tags are 1.1.0, 1.1.1, and 1.1.2, dated 9, 13, and 14 July 2026, with the last push to the repository in September 2026. Three patch releases inside a week suggests active bug fixing rather than a frozen project. The platform packages are versioned separately: the Android artifact in the README is pinned at `ai.sqlite:sync:1.0.0`, which does not match the 1.1.x release tags. That version skew is worth tracking, because a fix in the C extension may not be present in the packaged artifact you install from Maven Central or pub.dev. The README also links a coverage badge that reads a percentage from a generated report, but coverage of the extension's functions says nothing about whether the merge semantics match your data model.
The upgrade cost is the part that is genuinely unknown from this material. Because `cloudsync_init` alters how a table replicates, a version change that modifies the on-disk CRDT metadata would affect every replica you have already deployed. The README does not describe a migration path or a compatibility guarantee across versions. If you adopt this, pin the version, keep a copy of a synced database from before the upgrade, and test the upgrade against that copy rather than against a fresh schema.
Editorial conclusion
Adopt sqlite-sync if your application already keeps its state in SQLite and you need independent writes from several devices or agents to converge without a hand-written merge layer. Do not adopt it if you need a documented CRDT type per column, if your schema depends on foreign key enforcement across synced tables, or if your data lives in a database engine other than SQLite. Before committing, verify three things in your own environment: that cloudsync_init returns successfully on a copy of your real schema, that the text columns you care about are covered by Block-Level LWW rather than whole-value replacement, and which licence the release artifact you download actually carries, since the repository does not declare one.
Community notes