Different architectures, different ownership boundaries.
This page compares Kizuna with other offline sync products and with a sync layer a team builds itself, from the point of view of a team whose app already runs on Supabase. The matrix puts their capabilities side by side, and the sections below it explain how Kizuna differs from each product and when that product fits better. Facts about the other products come from their official sources as read on , and every source is listed under primary sources. Prices and benchmarks are out of scope. If a fact is wrong or out of date, open an issue with a link to the page that shows the current behavior.
Matrix
Supported
Partly supported
Not supported
Not documented
Offline sync capabilities of Kizuna and 11 alternatives, researched on 2026-10-08
Capability
Kizuna
PowerSync
Electric
Zero
Legend-State
RxDB
WatermelonDB
TinyBase
Triplit
InstantDB
Firestore
Custom
Where it runs
Your data travels only between your users' devices and your own Supabase project, and no server run by the library vendor or anyone else stores or relays it.
Kizuna
No third-party servers in the data path
Devices call the RPCs the SQL pack installs in your Supabase project; nothing Kizuna-operated sits in between.
PowerSync Cloud is the documented setup path, and Open Edition or Enterprise can be self-hosted so the sync service stays on machines you run beside your own database.
Electric Cloud is documented as the simplest host, and the same sync service can run from the official Docker image against your own Postgres, including Supabase.
WatermelonDB is only a local database and tells you to sync with a backend you operate, so application data is not stored or relayed by a Watermelon vendor server.
The Supabase persister reads and writes your own Supabase project over that project's REST and Realtime APIs, and the WebSocket synchronizer is a server process you start.
PartyKit, PowerSync, and hosted LibSQL persisters are separate third-party services.
The CLI and create-instant-app use Instant Cloud by default, so application data is stored on Instant's servers unless you switch the API URI to a server you host.
Self-hosting on your own Postgres and object storage is documented.
syncedSupabase talks to an existing Supabase project through a supplied supabase-js client, including Postgres tables and Realtime, and Auth is whatever session that client already has.
replicateSupabase syncs RxDB collections with tables in an existing Supabase Postgres project through supabase-js, and the guide uses that project's anon key.
Triplit documents a Supabase Auth integration so the Triplit server can accept that project's user JWTs, while the rows themselves stay in the Triplit database.
This uses neither the Supabase project's Postgres tables nor its Storage, and application data still moves to Triplit.
Instant stores application data in its own multi-tenant database or in a self-hosted Instant database, and the docs do not connect that data to an existing Supabase project.
Instant's backend uses PostgreSQL internally, but it is Instant's own database.
The Firestore client uses Cloud Firestore, and Supabase's official Firebase wrapper only reads Firestore into Postgres as foreign tables rather than running against an existing Supabase project.
Supabase also documents a one-time copy of Firestore data into Postgres.
You pass a user id from whatever auth provider you already use, and Zero forwards cookies or a generic bearer token to query and mutate endpoints you authenticate.
The Supabase persister uses the supabase-js client you pass, and the docs tell you to gate that table with the signed-in session through auth.uid() row-level security, with no second login.
That session applies to the persister and Supabase Realtime.
Storage is reachable from supabase-js, but you design and maintain the upload queue, the retries, and the link between files and rows.
Postgres Row Level Security checks every sync read and write under the signed-in user's JWT.
Kizuna
Row Level Security on every sync read and write
Pull and push read and write your rows under the caller's JWT, so your RLS policies decide what each pull returns and judge each queued mutation like an online write.
The Supabase guide tells browser clients to use the anon key and to write RLS policies that check auth.uid(), so each synced read and write is authorized as that user.
Supabase persister reads and writes are denied until a Postgres RLS policy allows them, and the docs show policies aimed at the authenticated role and auth.uid().
Authorization is Triplit schema filters on the client's JWT, and the documented server stores data in SQLite, LMDB, or memory, outside the reach of Postgres RLS.
External JWTs, including Supabase Auth, can be verified by the Triplit server.
Every mobile and web request is checked by Cloud Firestore Security Rules, which can read Firebase Authentication state, and the docs do not use Postgres row level security.
Rules can read request.auth when the app uses Firebase Authentication or Identity Platform.
supabase-js requests carry the user's JWT, so your policies apply as long as sync never uses a service-role key.
Each table is configured on its own: direction (read-write or pull-only), the rows each user receives (bucket), conflict mode, delete handling, and retention.
Kizuna
Sync settings per table
Each table has its own sync mode, bucket column, conflict mode, soft-delete handling, and tombstone retention.
Electric syncs reads out of Postgres only, so write direction, conflicts, and deletes are not sync settings, while a shape can still filter the rows of one table.
A publication and read permissions can limit which tables and rows each client receives, but conflicts and deletes are mutator code rather than per-table sync settings.
Each replicateSupabase call can filter rows, name the soft-delete column, and set pull and push, while conflicts stay on the Sync Engine's per-collection handler.
RxDB's sync engine also replicates over HTTP, GraphQL, CouchDB, Firestore, WebRTC, and other plugins, so a non-Supabase Postgres database is reached through your own endpoint.
Non-Supabase Postgres is your endpoint, not a built-in Postgres wire client.
The server can store its own data in SQLite or LMDB, and on Cloudflare in a Durable Object store, but it does not attach to an existing Postgres database.
A self-hosted Instant server requires PostgreSQL 17 with logical replication, so the backend can run on Postgres you operate, but only as Instant's own database.
The client cannot query an existing application schema or a non-Postgres database.
Cloud Firestore is a hosted NoSQL document database, and the official docs do not show it running on Postgres or on some other database you already operate.
Local persistence is a key-value plugin (localStorage, IndexedDB, MMKV, AsyncStorage, or the Expo SQLite kv-store), not relational tables that mirror server tables.
The Expo plugin imports expo-sqlite/kv-store, which is a key-value API on top of SQLite.
A SQLite storage exists, but it stores JSON documents and queries them with SQLite JSON functions rather than relational tables that mirror server tables.
The Supabase guide tells browsers to use localStorage or IndexedDB.
The on-device store is a cache of Firestore documents, using IndexedDB on the web and a persistent disk cache on Apple and Android, not SQL tables that mirror server tables.
The Supabase plugin can filter the listed rows, pass a Realtime filter, and with changesSince last-sync list only rows changed since the previous sync.
Sync servers track the queries a client has active and send new results to the queries a transaction affects, so a client does not subscribe to every row by default.
A client caches documents it has already received from the backend, and the docs do not define a declared per-user, tenant, or bucket subset to keep in sync.
Security rules can reject reads the signed-in user may not make, which limits what enters the cache, but that is authorization rather than a sync partition.
Online clients receive other people's changes through Instant's sync servers as a live update, and the docs tell you to open another tab to see them without refreshing.
When the master state has moved, the server rejects the push by returning the current master document, and the client conflict handler builds the next state.
The result is the master document, not a textual applied or rejected reason.
If any pushed record changed on the server since the last pull, the whole push is aborted, local changes stay unpushed, and the server returns an error.
That is one error for the batch, not an applied or rejected reason per write.
The official README says optimistic updates and rollbacks are handled for you, and it does not document an applied or rejected reason on each queued write.
Schema violations throw, and permission rules can deny create, update, or delete.
When the same record changes locally and remotely, the client keeps the server version and reapplies only the columns changed locally since the last sync.
A MergeableStore stamps each cell update with its own hybrid logical clock, so later merges can keep the latest value of each cell rather than replacing the whole row.
Each attribute has its own timestamp, so concurrent edits to different attributes of one entity both remain, and the same attribute is last-writer-wins.
The FAQ calls the same-attribute case a last-writer-wins register using Lamport timestamps, not a semantic merge of two values.
Not documented in InstantDB's official sources as of October 2026.
Firestore
Column-level conflict merge
Offline changes to the same document follow last write wins, so concurrent edits are not documented as a field-level merge that keeps both writers' columns.
update() can change chosen fields without replacing the whole document, and increment is applied on the server.
You choose and implement the merge rule, because a plain upsert overwrites the whole row.
A group of writes is applied together or not at all.
Kizuna
All-or-nothing write batches
The engine and the wire protocol carry atomic batches, but the app client API exposes no way to create one, so each write is pushed as its own mutation.
A local transaction buffers several mutations and can roll them back in the store, but the docs describe listener batching rather than one all-or-nothing submission to the server.
The Supabase persister then saves the whole store as one JSON row, so one save is a single document write, not a multi-statement Postgres transaction of your tables.
The update plugin's $inc and array operators change the local document, and the Supabase plugin then pushes the resulting document rather than a server-side field operation.
The CRDT plugin stores those operators so a custom backend could replay them.
Not documented in TinyBase's official sources as of October 2026.
Triplit
Server-side increment and array operations
Set attributes are documented to accept concurrent additions and removals, which covers part of server-side collection edits, and numeric increment is not documented.
The FAQ says arrays are unsupported because concurrent pushes would overwrite the same index.
The documented writes are update, merge, delete, and link or unlink, and update replaces the attribute you send rather than applying a server increment or array operator.
Bucket history is compacted and checked with checksums, so a long-offline client catches up incrementally, and a checksum mismatch re-syncs the bucket.
Compaction replaces old operations with MOVE and CLEAR markers.
Zero says it is a poor fit for long periods offline and does not document retained client tombstones plus an automatic rebuild after a retention window.
If the Postgres replication slot is invalidated, zero-cache re-syncs its server replicas from scratch.
Not documented in Triplit's official sources as of October 2026.
InstantDB
Long offline periods converge, deletes included
Not documented in InstantDB's official sources as of October 2026.
Firestore
Long offline periods converge, deletes included
Deletes use delete(), and for a long-offline client the docs describe periodic cleanup of older cached documents and last write wins, with no tombstone retention window that forces an automatic rebuild.
Not documented in Triplit's official sources as of October 2026.
InstantDB
Published protocol and conformance suite
Not documented in InstantDB's official sources as of October 2026.
Firestore
Published protocol and conformance suite
Google publishes the Cloud Firestore RPC reference, including Listen and a Write stream, and the pages reviewed do not include a conformance test suite for that protocol.
An attachments plugin stores files beside documents, and the sync engine can put attachment data on push and pull when the replication plugin supports it.
The docs say not every replication plugin replicates attachments, and the Supabase plugin page does not mention them or Supabase Storage.
A store holds tabular cells and keyed values, and neither the Supabase persister nor the synchronizers document a file or media queue tied to those rows.
Not documented in Triplit's official sources as of October 2026.
InstantDB
Files sync with their rows
Storage uploads a file, returns a $files id, and you link that id to another entity, so file metadata syncs as rows while the bytes use a separate upload call.
The upload is not documented as part of the mutation outbox.
Not documented in TinyBase's official sources as of October 2026.
Triplit
Resumable large uploads
Not documented in Triplit's official sources as of October 2026.
InstantDB
Resumable large uploads
Self-hosting requires the backend's object-storage role to manage multipart uploads, and the client upload guide does not document resume after an interrupted browser upload.
Firebase lists ReactFire and Vuefire for React and Vue and says those framework libraries are not official Firebase SDKs and are not covered by Firebase support.
First-party persisters save a store with expo-sqlite, react-native-sqlite-storage, and react-native-mmkv, and React hooks come from the ui-react module.
The official JavaScript SDK lists Cloud Firestore on React Native with persistence excluded, and React Native Firebase is listed as a library that is not an official SDK.
The same environments page names Expo only for a Cloud Storage base-64 polyfill, not for Firestore disk persistence.
Not documented in Triplit's official sources as of October 2026.
InstantDB
Many browser tabs, one database
The React guide tells you to open another tab and watch the same query update live, which shows server sync across tabs rather than one shared local engine.
The architecture essay says the web cache is IndexedDB.
The Supabase guide shows the SQL and client setup you apply yourself, and the repository scripts are build and test commands, not a provisioner for your project.
Google's Cloud blog calls Cloud Firestore generally available, and the current offline-persistence guide presents the feature without an alpha or beta label.
The JS reference marks setIndexConfiguration, for local query indexes, as Public Preview.
A custom build has no vendor release, so its maturity depends on the tests and operations your team provides.
PowerSync
Kizuna vs PowerSync
PowerSync is a sync service with client SDKs. The PowerSync Service replicates a source database and streams the rows each user should have, chosen by SQL queries called Sync Streams, into a SQLite database on the device. Local writes wait in an upload queue that calls an uploadData function you write, and that function sends them to your backend. The service runs as PowerSync Cloud or self-hosted, and it connects to Postgres, including Supabase, and to MongoDB, with MySQL and SQL Server in Beta.
How Kizuna differs
A PowerSync client connects to the PowerSync Service, which runs beside the source database in PowerSync Cloud or on servers you operate. Kizuna devices call the RPCs that the SQL pack installs in your Supabase project, with nothing Kizuna-operated in between (Architecture).
Sync Streams choose the rows a PowerSync device downloads, and Row Level Security applies to the writes that reach Postgres under the user's JWT. In Kizuna, pull and push read and write your rows under the caller's JWT, so your RLS policies decide which rows a device receives as well as whether each queued write applies (Server-side validation).
When your backend rejects a write, PowerSync's docs tell it to answer HTTP 2xx anyway; once the upload queue empties, local rows are brought back in line with server state and the discarded write disappears without a rollback call. Kizuna's push RPC answers each mutation with a typed verdict, and the engine reverts a rejected write and records it in the rejection journal (Offline writes).
PowerSync's upload queue records PUT, PATCH, and DELETE. Kizuna adds increment, arrayUnion, and arrayRemove, which the server applies to the stored value, so two devices that each add one both count (Collaborative fields).
Attachments in PowerSync use helpers that its feature-status page lists as Alpha, with an upload and download adapter you write and no documented hash check of downloaded bytes. Kizuna's attachment queue moves files through Supabase Storage, switches to resumable TUS uploads above 6 MiB, and checks each download against its SHA-256 digest (Media attachments).
Choose PowerSync when
You need a generally available product now. PowerSync lists its Postgres connector, Sync Streams, and main client SDKs as GA, and Kizuna is in Alpha (Project status).
Your data lives outside Supabase, in another Postgres host, MongoDB, MySQL, or SQL Server, or you want the choice between a managed sync service and a self-hosted one.
The rows a user receives depend on more than one equality column. Sync Streams select rows with SQL queries, while a Kizuna bucket is one equality column per table (Sync rules & buckets).
You want server changes streamed to connected clients over HTTP or a WebSocket. Kizuna pulls when an optional Realtime wake-up arrives and polls every 15 seconds by default (How Kizuna works).
Electric
Kizuna vs Electric
Electric syncs subsets of Postgres data, called shapes, into local apps. A shape is one table with an optional where clause and column list. The sync engine is an Elixir service that you run from the official Docker image or use on Electric Cloud. It connects to Postgres 14 or newer through logical replication and serves shapes over an HTTP API, which the TypeScript client and the React hooks consume. Its writes guide states that Electric does not do write-path sync, so your app sends writes through an API of its own.
How Kizuna differs
Electric's sync service runs next to Postgres, on Electric Cloud or on infrastructure you deploy, and caches shape logs on its host disk. Kizuna's protocol runs as SQL inside your project, so nothing runs next to Supabase (Architecture).
Electric covers the read path only. Kizuna carries writes too, through a durable outbox on the device, a push RPC in your project, and a typed verdict for each mutation (Offline writes).
Electric authorizes reads in an HTTP proxy or with a shape-scoped token from a gatekeeper you build. Kizuna forwards the user's Supabase session on every pull and push, and your RLS policies decide what each request may read and write (Server-side validation).
Electric's usual client holds shape rows in memory, its documented clients are TypeScript and Elixir, and its Postgres Sync docs describe no file sync. Kizuna keeps synced tables in local SQLite, runs the same Rust engine behind its Swift and Kotlin clients, and queues attachments through Supabase Storage.
Choose Electric when
You want read-path sync of Postgres into web or Expo apps, and you already have a write API or plan to design one.
Your Postgres runs outside Supabase. Electric works with any standard Postgres 14 or newer that has logical replication enabled.
You want live updates over Server-Sent Events or long polling from a generally available release. Electric 1.0 shipped in March 2025 with stable APIs, and Kizuna is in Alpha.
Zero
Kizuna vs Zero
Zero is a query-driven sync engine for TypeScript apps, designed in its own words to be as close to a classic web app as a sync engine can be. A server called zero-cache replicates Postgres 15 or newer into a SQLite replica, and each client receives the rows its ZQL queries name over a WebSocket. Writes run as mutators, functions that apply optimistically on the client and then run in a transaction against your database through a push endpoint you host. You run zero-cache yourself or use Cloud Zero, which the Zero team operates.
How Kizuna differs
Zero does not support offline writes, and its fit guide names long periods offline as a reason to pick something else. Kizuna is offline-first: writes wait in a durable outbox and replay after reconnect, and a device whose cursor is older than the tombstone retention window (30 days by default) rebuilds its local copy (Offline writes, Conflict resolution).
Zero needs zero-cache beside your database, a server that keeps a SQLite replica of the published Postgres data. Kizuna adds no server (Architecture).
Zero authorizes reads and writes in your query and mutator code, and its auth guide says it has no permission system like RLS. Kizuna applies your RLS policies to every pull and push under the user's Supabase JWT (Server-side validation).
Zero supports TypeScript clients only, and on the web it stores sync state in IndexedDB. Kizuna keeps synced tables as SQLite tables on every platform it runs on (How Kizuna works) and ships Swift and Kotlin clients over the same Rust engine (Swift and Kotlin).
Choose Zero when
Your app is mostly online, and you want query-driven partial sync with live updates over a WebSocket.
You want each write to be a mutator: several changes in one function that commits or rolls back as a unit on the client and the server, with raw SQL available on the server side.
You need a generally available product. Zero has been GA since March 2026, and Kizuna is in Alpha.
Your Postgres runs outside Supabase, on RDS, Aurora, Neon, Cloud SQL, or in Docker.
Legend-State
Kizuna vs Legend-State
Legend-State is a JavaScript library built on observables, with a sync engine and plugins for backends and for local persistence. Its Supabase plugin, syncedSupabase, reads and writes your tables through the supabase-js client you pass and can subscribe to changes through Supabase Realtime. With the retrySync option, pending changes keep retrying after a restart until they sync. Like Kizuna, it needs no server beside Supabase, and its requests carry the user's session, so RLS applies. The linked docs describe version 3, which is in beta.
How Kizuna differs
Legend-State persists observables through a key-value plugin (localStorage, IndexedDB, MMKV, AsyncStorage, or the Expo SQLite key-value store), and its select and eq calls build the remote supabase-js request. Kizuna keeps synced tables in local SQLite and runs kizunasync.from('todos').select().eq(...) against that local copy (Supported query operators).
The Supabase plugin cannot list rows deleted in Supabase, so Legend-State asks for a soft-delete column, and its docs describe no retention window for deletes. Kizuna records hard deletes as tombstones, keeps them 30 days by default, and rebuilds the local copy of a device whose cursor is older (Conflict resolution).
Legend-State retries an update whose function throws an error, and its docs describe no per-write applied or rejected result. Each Kizuna mutation gets a typed verdict; a rejected write is reverted locally and kept in the rejection journal (Offline writes).
updatePartial sends only the changed fields, and the docs describe no merge of concurrent edits and no server-side increment. Kizuna keeps concurrent edits to different columns and applies increment, arrayUnion, and arrayRemove on the server (Collaborative fields).
Legend-State's sync runs in JavaScript, with first-party React and React Native bindings, and its docs describe no file sync. Kizuna adds Swift and Kotlin clients and an attachment queue on Supabase Storage (Swift and Kotlin, Media attachments).
Choose Legend-State when
You want observables as your app state and a sync plugin that talks to Supabase directly, with your own filters, Realtime subscription, and persistence plugin.
A local key-value copy with soft deletes fits your data, and you do not need a server verdict for each write.
You also sync with Keel, Firebase Realtime Database, or a CRUD API, which Legend-State has plugins for.
RxDB
Kizuna vs RxDB
RxDB is a local document database for JavaScript runtimes, including the browser, Node.js, Electron, Capacitor, and React Native, with a replication engine and plugins for HTTP, GraphQL, CouchDB, Firestore, WebRTC, and Supabase. Its Supabase plugin syncs RxDB collections with tables in your project through supabase-js, straight from the device with no sync server in between. Live changes arrive through Supabase Realtime, and RLS policies you write decide what each user reads and writes.
How Kizuna differs
RxDB stores JSON documents and queries them with Mango selectors; its SQLite storage keeps those documents as JSON and is a premium plugin for production use. Kizuna keeps your synced tables as SQLite tables and queries them with the supabase-js call shape (Supported query operators).
The Supabase plugin needs a soft-delete column, because clients would miss a hard delete, and RxDB's docs describe no retention window. Kizuna records hard deletes as tombstones for 30 days by default and rebuilds the local copy of a device whose cursor is older (Conflict resolution).
When a push conflicts, RxDB returns the current server document and the client's conflict handler decides, and the default handler keeps the server document and drops the local change. Kizuna answers each mutation with a typed verdict and keeps concurrent edits to different columns (Conflict resolution).
RxDB applies $inc and array operators to the local document and pushes the resulting document. Kizuna sends increment, arrayUnion, and arrayRemove as transforms the server applies, so two devices that each add one both count (Collaborative fields).
Choose RxDB when
You want a document model, a choice of storage engines and plugins, and replication to backends other than Supabase.
Your app runs in Electron, Capacitor, or Node.js as well as in the browser and React Native.
You want a library that carries no alpha or beta label and syncs to Supabase with no server in between. RxDB has been developed since 2016 and reports production use, and Kizuna is in Alpha.
WatermelonDB
Kizuna vs WatermelonDB
WatermelonDB is a local database for React Native and the web, backed by SQLite on iOS, Android, and Node.js and by LokiJS over IndexedDB in the browser, with React bindings through withObservables. Its sync client calls pullChanges and pushChanges functions you write against two endpoints on your backend that implement the Watermelon Sync Protocol. In its own words, Watermelon is only a local database, and you bring your own backend.
How Kizuna differs
WatermelonDB leaves the server side to you: the two endpoints, change tracking, and a push that applies all changes in one transaction. Kizuna installs that server side in your Supabase project, with pull and push RPCs, triggers that record changes, and tombstone retention (What Kizuna installs).
Sync covers the whole local database at once, and the pull returns every collection. Kizuna pulls each table by its bucket, so a device copies only the rows its user is meant to have (Sync rules & buckets).
If any record in the push changed on the server since the last pull, WatermelonDB aborts the whole push with one error, and the next sync merges per column with local changes winning. Kizuna returns a typed verdict for each mutation, and competing edits to one column follow server arrival order or a Hybrid Logical Clock (Conflict resolution).
WatermelonDB documents no Supabase Auth support and no file sync. Kizuna forwards the user's Supabase session and syncs attachments through Supabase Storage (Media attachments).
Choose WatermelonDB when
You want a local database for React Native with observable queries, and you already own a sync backend or want a documented, backend-neutral protocol to implement on any server. Its maintainers describe it as feature-complete, and it has powered Nozbe Teams since 2017, while Kizuna is in Alpha.
TinyBase
Kizuna vs TinyBase
TinyBase is a reactive in-memory data store for local-first JavaScript and TypeScript apps. Persisters save a store to browser storage, SQLite, PostgreSQL, or Supabase, and synchronizers merge stores over WebSocket, BroadcastChannel, or Cloudflare Durable Objects. A MergeableStore stamps each cell update with a hybrid logical clock, so merges keep the latest value of each cell. Its Supabase persister reads and writes one JSON serialization of the whole store as a single row of a table you create.
How Kizuna differs
Kizuna syncs the rows of your existing Postgres tables in place. The TinyBase Supabase persister stores the whole store as one JSON row, so your RLS policies see one row rather than each record (Architecture).
A MergeableStore merge is decided on the client by clock order, and the docs describe no per-write result from a server. Kizuna applies each write on the server and returns a typed verdict, with a named reason such as RLS_DENIED, PRECONDITION, CONSTRAINT, or DELETE_WINS when it refuses one (Server-side validation).
A TinyBase store lives in memory, syncs as a whole, and runs in JavaScript and TypeScript only. Kizuna keeps synced tables in SQLite, pulls each table by its bucket, and has Swift and Kotlin clients (Sync rules & buckets, Swift and Kotlin).
Choose TinyBase when
Your data is a per-user or per-document store that fits in memory, you want a reactive local store with queries, and a merge decided on the client is acceptable.
You want stores to merge across tabs, devices, or a server you run, over BroadcastChannel, WebSocket, or Cloudflare Durable Objects.
You want React, Solid, or Svelte bindings, or persistence to SQLite, PostgreSQL, PGlite, or LibSQL as well as Supabase.
Triplit
Kizuna vs Triplit
Triplit is a database designed to run in any JavaScript environment, paired with a sync server. The Triplit server holds the remote database in SQLite, LMDB, or memory, on Triplit Cloud or on a server you deploy. Clients subscribe to queries over a WebSocket, receive only data they do not have yet, and keep a local copy and an outbox in IndexedDB or memory. Each attribute carries its own timestamp, so concurrent edits to different attributes of one entity both remain, as they do across columns in Kizuna (Conflict resolution).
How Kizuna differs
Triplit keeps application rows in the Triplit server's own database, next to Supabase. Its Supabase integration lets that server accept Supabase Auth tokens, while the data stays outside your Postgres. Kizuna syncs your existing tables in place (Architecture).
Permission filters in the Triplit schema, checked against the client's token, authorize reads and writes. Kizuna relies on the Postgres RLS policies you already have, evaluated under the caller's JWT (Server-side validation).
Triplit rolls back and retries failed updates, and its docs do not show a per-write applied or rejected result with a reason. Kizuna returns a typed verdict for each mutation and records rejections in a journal your app can show (Offline writes).
You want one TypeScript database on the client and the server, with query subscriptions over a WebSocket, attribute-level last-writer-wins merge, and sets that accept concurrent additions and removals.
Your application data can live in a Triplit server you run or in Triplit Cloud, with Supabase Auth signing users in.
You want React, Solid, Vue, Svelte, or React Native and Expo bindings, and its AGPL-3.0 license fits your project.
InstantDB
Kizuna vs InstantDB
InstantDB is a backend with client SDKs. Apps read with InstaQL, a declarative query syntax the docs compare to GraphQL, write with db.transact, and receive other users' changes live through Instant's sync servers. The web client keeps a local cache in IndexedDB and a persistent outbox for offline writes. Data lives in Instant's own database, on Instant Cloud or on a server you host with Postgres 17 and object storage. Instant's site says the service is sunsetting and that Instant Cloud services continue until August 31, 2027; self-hosting remains documented.
How Kizuna differs
Instant stores application data in its own database, and its docs do not connect that data to an existing Supabase project. Kizuna syncs the tables you already have, in place (Architecture).
Every read and write in Instant passes its own permission rules, written in CEL, and users sign in through Instant's own auth. Kizuna uses your Supabase session and your RLS policies (Server-side validation).
Instant handles optimistic updates and rollbacks and documents no applied or rejected reason for each queued write. Kizuna returns a typed verdict for each mutation and keeps rejections in a journal (Offline writes).
Instant Storage uploads a file in its own call. The docs do not describe that upload as part of the outbox, and they describe no resume after an interrupted browser upload and no hash check on download. Kizuna queues attachments next to the outbox, resumes TUS uploads above 6 MiB, and verifies each download against SHA-256 (Media attachments).
Choose InstantDB when
You want queries, transactions, permissions, auth, and file storage in one backend, with JavaScript SDKs for React, Vue, React Native, Svelte, and SolidJS. Plan to self-host it, or to move before Instant Cloud services end on August 31, 2027.
Firestore
Kizuna vs Firestore
Cloud Firestore is a cloud-hosted NoSQL document database from Google that Apple, Android, and web apps reach directly through native SDKs. The SDKs cache the documents an app uses for offline access, queue writes while offline, and deliver changes to onSnapshot listeners. Cloud Firestore Security Rules, which can read Firebase Authentication state, check every client request.
How Kizuna differs
Firestore is a separate Google backend, so a Supabase app would move its data there. A login that is not Firebase Auth needs your server to mint Firebase custom tokens. Kizuna runs inside your Supabase project under the user's Supabase session (Architecture).
Firestore resolves several offline changes to the same document by last write wins. Kizuna keeps concurrent edits to different columns of a row, and competing edits to one column follow server arrival order or a Hybrid Logical Clock (Conflict resolution).
Firestore documents no tombstone retention window for a client that stays offline a long time. Kizuna keeps tombstones for 30 days by default and rebuilds the local copy of a device whose cursor is older (Conflict resolution).
Files go through Cloud Storage for Firebase, a separate product with its own upload API. Kizuna queues attachments next to the outbox and checks each download against SHA-256 (Media attachments).
Choose Firestore when
Your app is not on Supabase, or you are ready to move to Firebase, and you want a generally available, Google-hosted document database with first-party Apple, Android, and web SDKs.
You want live listeners, offline persistence that is on by default on Apple and Android, atomic batches of up to 500 writes, and server-side increment, arrayUnion, and arrayRemove, the transforms Kizuna models its own on (Introduction).
Your documents stay under the 1 MiB limit, and last write wins per document is acceptable.
Custom implementation
Kizuna vs a typical custom implementation
A typical custom implementation is the sync layer a team writes on top of supabase-js: a local store on the device, a queue of pending writes, pull queries with a checkpoint per user, and a Realtime subscription that triggers a refresh. The app talks to Supabase directly with the user's session, so RLS applies and no third-party server sits in the data path. Its guarantees are whatever its code and tests establish, and Consistency model lists the guarantees and limits Kizuna commits to, and a custom design has to answer the same questions.
How Kizuna differs
A custom build designs its own outbox, ordering, and retries. Kizuna ships a durable outbox, a typed verdict for each mutation, a local revert of rejected writes, and a rejection journal (Offline writes).
A plain upsert overwrites the whole row. Kizuna merges per column and applies increment, arrayUnion, and arrayRemove in the database (Conflict resolution, Collaborative fields).
A custom build has to track deletes, choose a retention window, and rebuild clients that were away too long. Kizuna numbers changes at commit so a pull never skips a late transaction, keeps tombstones, and rebuilds a device whose cursor has expired (Fencing and horizons).
Files need their own queue, TUS client, and integrity check. Kizuna's attachment queue resumes uploads above 6 MiB and verifies downloads against SHA-256 (Media attachments).
Each platform needs its own client, and the protocol stays private to the app. Kizuna runs one Rust engine behind its JavaScript, Swift, and Kotlin clients and publishes its protocol with a conformance corpus (Protocol overview).
Choose a custom implementation when
Your requirements are narrow enough that a design for one application fits better than Kizuna's Supabase-specific protocol.
Your team has the protocol expertise and will build the failure, migration, and long-offline tests the design needs.
You want to control every dependency, release, query shape, and operational trade-off yourself, or your backend is not Supabase.
The matrix cells of the other products link these pages, as read on . Each vendor can change its pages at any time, which is why the page carries that date.
All trademarks belong to their respective owners, and no endorsement is implied. Some apps need a different kind of tool: concurrent text or canvas editing needs a CRDT system, a transactional invariant across users needs an online-only flow, and sync between devices with no server in the middle needs a peer-to-peer system, because Kizuna routes every change through your Supabase project (Design trade-offs). Product & fit lists the cases where Kizuna is the wrong tool.