smugglr

compare · honest, falsifiable, no marketing

Two SQLites. One wire. No server.

smugglr is a sync engine. CLI, Rust crate, npm package. It pushes, pulls, syncs, and broadcasts. There is no service you have to operate, it is free*.

01 · the lay of the land

You are not building a database. You are moving rows between places that already have one.

SQLite is everywhere already. The phone has it. The laptop has it. Cloudflare D1 is it. Turso is it. Datasette and rqlite and StarbaseDB run on it. The hard part of "local-first" or "multi-device" or "edge-distributed" is not picking a database; it is keeping every node holding the same rows.

smugglr is one tool that does that one job. SQLite on every node. Content-hash diff between them. Run it as a CLI, embed it as a Rust crate, install the npm package in the browser or Node. No central server. No managed cloud. No per-row pricing.

The matrix below names every comparable tool and what it actually solves. Building a multiplayer game? /compare/games/ is the games-shaped read.

»matrix·specific, falsifiable, dated 2026-04
## 01

matrix

# every comparable tool, scored on the same axes
ToolLaneServer requiredConflict modelCost floor
smugglrSQLite to SQLite syncNoLast-received-wins, UUIDv7Free, MIT
LitestreamSQLite to S3 backupNoSingle writer assumedFree, Apache
rqlite, dqliteRaft-clustered SQLiteYes, the cluster itselfLinearizable via RaftFree, MIT / LGPL
CR-SQLiteCRDT extension to SQLiteNo (per-node), yes (relay)CRDT merge by constructionFree, MIT
Syncthing on .sqliteFile-level sync of SQLiteNoFile replace, corruption riskFree, MPL
Turso embedded replicasHosted libSQL with replicasYes, TursoAuthoritative server$0 to ~$29 / mo per scale
ElectricSQLPostgres to SQLite read syncYes, sync serviceServer is source of truthFree OSS, paid cloud
PowerSyncBackend DB to SQLite syncYes, PowerSync serviceLast-write-wins via serviceFree tier, then per-MAU
Replicache, ZeroClient cache + sync to backendYes, your push / pull / pokeLibrary reconcilesFree OSS lib, paid cloud
Yjs, AutomergeCRDT for collaborative docsNo (peer), often relayCRDT mergeFree, MIT
RxDB, WatermelonDBLocal-first reactive DBOften, for syncLibrary-definedFree OSS, premium plugins
Firebase Realtime DBHosted JSON treeYes, GoogleLast-write-wins, theirsFree Spark, then per-op
FirestoreHosted document DBYes, GoogleLast-write-wins, theirsFree Spark, then per-read / per-write
Supabase RealtimePostgres change broadcastYes, Supabase or self-hostPostgres is the truthFree tier, then per-message
ConvexHosted reactive backendYes, ConvexConvex transactionsFree tier, then per-op
MongoDB Atlas Device SyncHosted Realm with syncYes, AtlasRealm conflict resolutionFree tier, then per-MAU

Three columns are load-bearing. "Server required" answers whether you operate something other than your own app. "Conflict model" tells you what happens when two peers write the same row. "Cost floor" is the entry tier as advertised; the gap between free-tier usage and a real workload is where most billing surprises happen on per-op pricing models. smugglr's cost floor is your existing hosting bill.

02 · sync engine peers

The closest neighbors. SQLite-shaped, row-shaped, on the same shelf.

Six tools sit nearest smugglr. None do exactly what it does. The differences are operational, not philosophical.

Litestream

SQLite to S3. One way. Backup-shaped.

Litestream streams a SQLite WAL to S3. One source, many readers. It does not move rows between two live SQLite databases on different machines. smugglr does. Litestream answers "back this up to S3 continuously." smugglr answers "keep all my SQLite copies the same."

rqlite, dqlite

Raft-clustered SQLite. The cluster is the database.

Every node is a peer in a coordinated quorum. You operate a cluster. smugglr does not run a cluster; it moves rows between SQLite files that already exist. Pick rqlite if you want linearizable writes across nodes; pick smugglr if you want each node to own its own SQLite file and reconcile on a schedule.

ElectricSQL, PowerSync

Postgres on the server, SQLite on the client, sync service in the middle.

Both assume an authoritative Postgres backend and a sync service that runs in front of it. The model is one server, many local replicas. Conflict resolution is the service's job. smugglr does not assume an authoritative server. Every node is peer-symmetric. If you already run Postgres and want SQLite on your clients, ElectricSQL or PowerSync are the right tools. If you want SQLite end to end, smugglr is the right tool.

Turso embedded replicas

Hosted libSQL with read replicas pulled to the client.

Turso runs the authoritative database. Replicas are read-only on the client; writes go back to Turso. smugglr ships a Turso target, so smugglr writes into Turso the same way it writes into a local file. Turso embedded replicas tie you to Turso. smugglr ties you to SQLite. Both are fine choices; the question is whether you want a vendor or a file format.

CR-SQLite

CRDT extension to SQLite. Concurrent writes merge by construction.

The cost is the table model: every column gets versioned metadata, schema migrations are heavier, the merge semantics are the ones CRDTs offer. smugglr is the opposite trade. Plain SQLite tables. Last-received-wins on divergence. UUIDv7 primary keys to avoid collisions in the first place. CR-SQLite is the right call when divergent concurrent writes on the same row are common; smugglr is the right call when they are rare.

Syncthing on a .sqlite file

Filesystem sync. Works until two nodes write at once.

Filesystem sync of a SQLite file works until two nodes write at once. Then the file is corrupt, not merged. smugglr operates at the row level using content hashes, so concurrent writes on different tables or different rows reconcile cleanly. If you tried Syncthing for SQLite and got a corrupted database, that is the reason.

03 · hosted services that solve part of the same problem

The shortcut option. Pay per operation, accept the lock-in, ship faster.

These services are real shortcuts to "users see fresh data on every device." They cost money per row and they own the schema. smugglr is the slower-to-set-up, cheaper-at-scale, lock-in-free alternative.

Firebase Realtime DB, Firestore

Hosted JSON tree or document store with realtime fan-out.

The fastest way to ship a multi-device app, until the bill arrives. Free Spark tier is generous; Blaze prices reads, writes, and storage individually. Costs scale non-linearly with a real workload. Lock-in is real: data lives in Google's schema, security rules are theirs, exports are work. smugglr does not host data. SQLite is the data. Migration cost off smugglr is choosing a different SQLite client; migration off Firebase is rewriting the data layer.

Supabase Realtime

Postgres logical replication, broadcast over a websocket.

The closest tool to "realtime that does not need C++." Real value when you already run Supabase Postgres. The cost is operational complexity: websockets behind a CDN, message-rate limits, connection ceiling. Teams hit those ceilings and drop it when the loop does not actually require sub-second push. If your loop is per-action rather than per-frame, smugglr's watch daemon and LAN broadcast cover the same shape without a websocket layer or a Supabase project.

Convex

Hosted reactive backend. Functions, queries, and live data in one platform.

Convex bundles a database, a function runtime, and a reactive client into one product. Lock-in is total: data, schema, query language, and runtime are theirs. The free tier is enough to evaluate; paid usage is per-operation. Convex is the right call if you want one vendor for the whole reactive backend and you accept the lock-in. smugglr is the right call if you want SQLite on every node and the freedom to host the rest of the stack yourself.

MongoDB Atlas Device Sync

Realm on the client, MongoDB Atlas on the server, sync between them.

The mobile-first incumbent. Realm is a real local database with reactive queries; Atlas is the cloud counterpart. Sync is bidirectional with Realm-defined conflict resolution. The cost is the document model and the per-MAU pricing on Atlas. smugglr is relational and free-as-in-MIT, with SQLite as the local store and your hosting as the server. If you need Realm's mobile reactivity primitives, Atlas Device Sync is the fit; if you want SQLite, smugglr is the fit.

04 · the client-cache layer

Reactive caches with mutators. Different shape from sync engines, often confused.

These libraries solve the front-end half of local-first: optimistic UI, reactive queries, mutator functions. They sync against a backend you operate. smugglr does not ship a reactive cache, but the npm adapters bridge to the most common ones.

Replicache, Zero

Client-side reactive cache plus push, pull, and poke against your backend.

You implement push and pull endpoints; the library handles the cache, the optimistic UI, and the reconciliation. The model is client to server, not peer to peer. smugglr does not ship a reactive cache. If you already use Zustand or Nanostores, the @smugglr/zustand and @smugglr/nanostores adapters persist your existing store to a local SQLite table and rehydrate it on every sync. Pick Replicache or Zero for their cache and mutator model; pick smugglr if you have a state store and want sync without rewriting the cache layer.

Yjs, Automerge

CRDT primitives for collaborative documents. Peer-to-peer or relay.

If your data is a document with concurrent edits at the character level, these are the right tools. Yjs powers Liveblocks, TipTap collab, and most production collaborative editors. Automerge ships JSON-CRDT primitives. smugglr is row-shaped, not character-shaped; concurrent writes on the same row resolve by last-received-wins, not by merge. Pick Yjs or Automerge for documents; pick smugglr for relational state.

RxDB, WatermelonDB

Local-first reactive databases for the browser and React Native.

Both ship a local store with reactive queries and pluggable sync backends. RxDB is JSON, WatermelonDB is SQLite-backed and React Native-shaped. They overlap with smugglr on the local-first claim and differ on the sync layer: RxDB and WatermelonDB ask you to bring or build a sync server; smugglr is the sync, and you bring the client. The npm package + state-store adapters cover the same use case in a thinner stack.

Liveblocks, Yjs providers

Hosted CRDT relays with presence, history, and webhooks.

Liveblocks is the managed-Yjs option: hosted relay, presence channels, comments, version history, webhooks. Per-MAU pricing. The right call when you are shipping collaborative document UX and do not want to operate the relay. smugglr is not in that lane; pair Liveblocks with smugglr if your app has collaborative documents (Liveblocks) and relational app state (smugglr) as separate concerns.

05 · four shapes that fit smugglr

Concrete jobs, named so you can recognize yours.

Dev, staging, prod, CI hold the same rows

The original use case. The reason this binary exists.

Your local dev box, your CI runner, your staging environment, and prod all want the same SQLite file kept in sync, sometimes one-way, sometimes bidirectional. smugglr handles the diff and the write with typed exit codes for the agents and pipelines that orchestrate it.

One app on phone, web, and desktop

Local-first SaaS. Notes, knowledge bases, task managers, journals.

The user expects every device to show the same data. The default answer is Firestore or Supabase; both meter you per row and own the schema. smugglr is the alternative: SQLite on every device, sync over the network you already have, no per-MAU bill, no vendor lock.

Edge replicas next to your users

Read-fast deployments without the data tier becoming the bottleneck.

You run Cloudflare Workers, Fly Machines, or regional VMs and want a local SQLite next to the application. smugglr ships into D1, Turso, rqlite, Datasette, StarbaseDB, and SQLite Cloud, so the same engine that syncs your laptop to staging syncs your origin to every region.

Agent memory that survives sessions

Coordinated state across machines, processes, and agent invocations.

An AI agent running on multiple boxes wants a shared SQLite for memory, decisions, and audit trail. smugglr ships JSON output, typed exit codes, and a watch daemon, so an agent can sync its own memory without parsing prose. Same engine, agent-shaped invocation.

06 · what smugglr is not

Honest about the rows it does not cover.

If you needThe right tool
Sub-100 ms collaborative textYjs, Automerge, Liveblocks. CRDT primitives, not row diffs.
Tick-rate game netcodePhoton, Mirror, Colyseus. See /compare/games/.
Postgres logical replicationNative Postgres, ElectricSQL. smugglr is SQLite to SQLite.
Hosted document databaseFirestore, MongoDB Atlas. smugglr does not store data; it moves it.
Reactive cache with mutatorsReplicache, Zero, Convex. smugglr does not own client state.
Pub-sub at the row levelSupabase Realtime, Liveblocks broadcast. smugglr pulls; it does not push events.

smugglr moves SQLite rows between SQLite databases. Run as a CLI, embed as a Rust crate, install the npm package. The shape of the problem stays the same: a config, a diff, a write.

07 · install

Three ways to use it. Same algorithm, same adapters, same verbs.

CLI

Run smugglr as a command-line tool.

  curl -fsSL https://smugglr.dev/install | bash

The curl installer covers macOS and Linux. On Windows, use cargo install smugglr or the release binary.

Rust crate

Embed the engine in a Rust service.

  cargo add smugglr-core

No CLI dependencies. Same diff, same targets, same verbs, called from your code.

npm package

Use it from the browser or Node.

  npm install smugglr

Pairs with @smugglr/zustand and @smugglr/nanostores when you want a sync layer behind your existing client store.

Source on github.com/rafters-studio/smugglr. MIT licensed.

  • Free as in OSS, not free as in beer, even though we do ship under MIT.