IndexedDB Sync
IndexedDB stores structured data inside a single browser, on a single device, in a single origin. That is the whole design. It has no concept of syncing that data to another tab, another device, or a backend server. As soon as your app needs the same data in more than one place, you have to build IndexedDB sync yourself, or use a library that ships it.
This page explains what IndexedDB sync means, why the native API gives you nothing for it, the different levels of sync you might need, and how RxDB adds realtime replication on top of IndexedDB.
What IndexedDB Sync Means
"Sync" is used for three different problems, and it helps to keep them apart:
- Multi-tab sync: Two browser tabs of the same origin each open the same IndexedDB database. A write in one tab must become visible in the other tab.
- Client-server sync: The browser holds a local copy in IndexedDB and a backend server holds the source of truth. Changes flow both ways so the client works offline and catches up when it reconnects.
- Peer-to-peer sync: Two or more clients exchange changes directly, without a central server, over WebRTC or a relay.
Native IndexedDB solves none of these. It only stores and reads data in one place.
Why Raw IndexedDB Has No Sync
IndexedDB was designed as a low-level storage building block, not a database engine. It gives you object stores, indexes, and transactions. It does not give you:
- Change events across tabs: There is no built-in event when another tab writes to the store. You have to broadcast changes yourself.
- A network layer: IndexedDB never talks to a server. It has no push, no pull, no protocol.
- Change tracking: There is no log of what changed since the last sync. To replicate you need to know which documents are new or updated, and raw IndexedDB does not record that.
- Conflict handling: When the same document is edited in two places while offline, something has to decide the winner. IndexedDB has no notion of document revisions.
So syncing IndexedDB is not a small helper on top of the API. You end up rebuilding change feeds, a revision system, and a replication protocol. That is a database.
Multi-Tab Sync
The first level of sync happens inside one browser. When a user opens your app in two tabs, both tabs read and write the same IndexedDB database, and a write in one tab should update the UI in the other.
The browser primitive for this is the BroadcastChannel API, which sends messages between tabs of the same origin. You can send a message on every write and have other tabs re-read the changed data. Doing this by hand is error-prone, because you also have to avoid running the same background work in every tab at once.
RxDB handles this out of the box. With multiInstance: true, writes in one tab are visible to reactive queries in every other tab, change events propagate over a BroadcastChannel, and leader election picks a single tab to run the server replication so you do not open one connection per tab.
import { createRxDatabase } from 'rxdb/plugins/core';
import { getRxStorageIndexedDB } from 'rxdb-premium/plugins/storage-indexeddb';
const db = await createRxDatabase({
name: 'mydb',
storage: getRxStorageIndexedDB(),
// Coordinate the same database across all tabs of this origin.
multiInstance: true
});Client-Server Sync
The second level is syncing the browser's IndexedDB copy with a backend. This is what most people mean by "IndexedDB sync". The client keeps working on the local database, and a replication process moves changes to and from the server.
To do this correctly you need three things that raw IndexedDB lacks:
- A checkpoint: a marker of the last successfully synced state, so a reconnect only sends what changed since then instead of everything.
- Change detection: a way to list documents modified since that checkpoint. RxDB stores an internal
_metafield and revision on every document for exactly this. - Conflict handling: a rule for when the same document was changed on both sides. RxDB uses per-document revisions and a conflict handler you can customize.
RxDB packages this into its Sync Engine. The backend does not have to run RxDB. You can replicate against any infrastructure through the general replication protocol or one of the ready-made plugins:
- GraphQL replication against a GraphQL endpoint
- HTTP replication against a plain REST server on top of PostgreSQL or MongoDB
- CouchDB, Firestore, Supabase, NATS, WebSocket, and more
import { replicateRxCollection } from 'rxdb/plugins/replication';
const replicationState = replicateRxCollection({
collection: db.todos,
replicationIdentifier: 'my-todos-http-replication',
pull: {
async handler(checkpointOrNull, batchSize) {
// Ask the server for documents changed since the last checkpoint.
const response = await fetch(`/api/pull?since=${/* checkpoint */ ''}`);
const data = await response.json();
return { documents: data.documents, checkpoint: data.checkpoint };
}
},
push: {
async handler(changeRows) {
// Send local writes to the server and return conflicts, if any.
const response = await fetch('/api/push', {
method: 'POST',
body: JSON.stringify(changeRows)
});
return response.json();
}
}
});Because the replication runs on top of the local database, reads and writes stay zero-latency. The user never waits for the network. The sync happens in the background and continues where it left off after the client goes offline and back online.
Peer-to-Peer Sync
The third level skips the central server. Clients exchange changes directly with each other. RxDB supports this with WebRTC replication, where peers connect through a signaling server and then sync documents directly. This suits collaborative apps where a backend is optional or where devices on the same network should sync without the cloud.
Sync Approaches Compared
There are a few ways to get sync onto IndexedDB. They differ in how much they hand you.
- Do it yourself: Use raw IndexedDB or a thin wrapper like Dexie.js and write the change tracking, checkpoint, and protocol by hand. Full control, but you are building and maintaining a replication engine.
- A syncing document store: PouchDB syncs IndexedDB with CouchDB. It works well but ties you to the CouchDB protocol and its revision-tree overhead grows the on-disk size.
- RxDB: A local-first database that uses IndexedDB (or faster storages) and ships multi-tab, client-server, and peer-to-peer sync with pluggable backends.
Feature Comparison
| Sync capability | Raw IndexedDB | Dexie.js | PouchDB | RxDB |
|---|---|---|---|---|
| Multi-tab change events | ❌ | ⚠️ manual | ⚠️ manual | ✅ built in |
| Leader election across tabs | ❌ | ❌ | ❌ | ✅ built in |
| Client-server replication | ❌ | ⚠️ paid add-on | ✅ CouchDB only | ✅ many backends |
| Offline then catch up | ❌ | ❌ | ✅ | ✅ |
| Change tracking / checkpoints | ❌ | ❌ | ✅ | ✅ |
| Conflict handling | ❌ | ❌ | ✅ revision tree | ✅ revisions + custom handler |
| Peer-to-peer sync | ❌ | ❌ | ⚠️ via CouchDB | ✅ WebRTC |
| Backend requirement | none | none | CouchDB | any (GraphQL, HTTP, more) |
FAQ
Can IndexedDB sync across devices on its own?
No. IndexedDB is scoped to one browser on one device and has no network layer. To sync across devices you need a replication process that moves changes through a server or a peer connection. RxDB provides this with its Sync Engine.
How do I sync IndexedDB between browser tabs?
Use the BroadcastChannel API to notify other tabs of writes, or let a database handle it. With RxDB and multiInstance: true, writes in one tab reach reactive queries in every other tab automatically, and leader election keeps a single tab responsible for the server connection.
Does IndexedDB sync work offline?
Yes, when the database tracks changes. The client reads and writes to the local IndexedDB copy while offline, and the replication sends the queued changes once the connection returns. This is the core of the offline-first approach that RxDB is built for.
What happens on a conflict when two devices edit the same document?
The sync layer needs per-document revisions to detect that both sides changed. RxDB attaches a revision to every document and runs a conflict handler that you can customize, so you decide whether the local write, the remote write, or a merge wins.
Follow Up
- Read how the RxDB Sync Engine works
- Start with the RxDB Quickstart
- Learn about offline-first apps and zero-latency interactions
- Compare the best IndexedDB wrappers
- Check the RxDB code on GitHub and leave a star ⭐