Skip to main content

Supabase Replication Plugin for RxDB - Real-Time, Offline-First Sync

Supabase

The Supabase Replication Plugin for RxDB delivers seamless, two-way synchronization between your RxDB collections and a Supabase (Postgres) table. It uses PostgREST for pull/push and Supabase Realtime (logical replication) to stream live updates, so your data stays consistent across devices with first-class local-first, offline-ready support.

Under the hood, the plugin is powered by the RxDB Sync Engine. It handles checkpointed incremental pulls, robust retry logic, and conflict detection/resolution for you. You focus on featuresโ€”RxDB takes care of sync.

2:36
Supabase in 100 Seconds

Key Features of the RxDB-Supabase Pluginโ€‹

  • Cloud Only Backend: No self-hosted server required. Client devices directly sync with the Supabase Servers.
  • Two-way replication between Supabase tables and RxDB collections
  • Offline-first with resumable, incremental sync
  • Live updates via Supabase Realtime channels
  • Conflict resolution handled by the RxDB Sync Engine
  • Works in browsers and Node.js with @supabase/supabase-js

Architecture Overviewโ€‹

Client A
Supabase
Client B
Client C

Clients connect directly to Supabase using the official JS client. The plugin:

  • Pulls documents over PostgREST using a checkpoint (modified, id) and deterministic ordering.
  • Pushes inserts/updates using optimistic concurrency guards.
  • Streams new changes using Supabase Realtime so live replication stays up to date.
note

Because Supabase exposes Postgres over HTTP/WebSocket, you can safely replicate from browsers and mobile apps. Protect your data with Row Level Security (RLS) policies; use the anon key on clients and the service role key only on trusted servers.

Setting up RxDB โ†” Supabase Syncโ€‹

1.

Install Dependenciesโ€‹

npm install rxdb @supabase/supabase-js
2.

Create a Supabase Project & Tableโ€‹

In your supabase project, create a new table. Ensure that:

  • The primary key must have the type text (Primary keys must always be strings in RxDB)
  • You have an modified field which stores the last modification timestamp of a row (default is _modified)
  • You have a boolean field which stores if a row should is "deleted". You should not hard-delete rows in Supabase, because clients would miss the deletion if they haven't been online at the deletion time. Instead, use a deleted boolean to mark rows as deleted. This way all clients can still pull the deletion, and RxDB will hide the complexity on the client side.
  • Enable the realtime observation of writes to the table.

Here is an example for a "human" table:

create extension if not exists moddatetime schema extensions;

create table "public"."humans" (
"passportId" text primary key,
"firstName" text not null,
"lastName" text not null,
"age" integer,

"_deleted" boolean DEFAULT false NOT NULL,
"_modified" timestamp with time zone DEFAULT now() NOT NULL
);

-- auto-update the _modified timestamp
CREATE TRIGGER update_modified_datetime BEFORE UPDATE ON public.humans FOR EACH ROW
EXECUTE FUNCTION extensions.moddatetime('_modified');

-- add a table to the publication so we can subscribe to changes
alter publication supabase_realtime add table "public"."humans";
3.

Create an RxDB Database & Collectionโ€‹

Create a normal RxDB database, then add a collection whose schema mirrors your Supabase table. The primary key must match (same column name and type), and fields should be top-level simple types (string/number/boolean). You donโ€™t need to model server internals: the plugin maps the serverโ€™s _deleted flag to doc._deleted automatically, and _modified is optional in your schema (the plugin strips it on push and will include it on pull only if you define it). For browsers use a persistent storage like Localstorage or IndexedDB. For tests you can use the in-memory storage.

// client
import { createRxDatabase } from 'rxdb/plugins/core';
import { getRxStorageLocalstorage } from 'rxdb/plugins/storage-localstorage';

export const db = await createRxDatabase({
name: 'mydb',
storage: getRxStorageLocalstorage()
});

await db.addCollections({
humans: {
schema: {
version: 0,
primaryKey: 'passportId',
type: 'object',
properties: {
passportId: { type: 'string', maxLength: 100 },
firstName: { type: 'string' },
lastName: { type: 'string' },
age: { type: 'number' }
},
required: ['passportId', 'firstName', 'lastName']
}
}
});
4.

Create the Supabase Clientโ€‹

Make a single Supabase client and reuse it across your app. In the browser, use the anon key (RLS-protected). On trusted servers you may use the service role keyโ€”but never ship that to clients.

//> client

import { createClient } from '@supabase/supabase-js';

export const supabase = createClient(
'https://xyzcompany.supabase.co',
'eyJhbGciOi...'
);
5.

Start Replicationโ€‹

Connect your RxDB collection to the Supabase table to start the replication.

//> client

import { replicateSupabase } from 'rxdb/plugins/replication-supabase';

const replication = replicateSupabase({
tableName: 'humans',
client: supabase,
collection: db.humans,
replicationIdentifier: 'humans-supabase',
live: true,
pull: {
batchSize: 50,
// optional: shape incoming docs
modifier: (doc) => {
// map nullable age-field
if (!doc.age) delete doc.age;
return doc;
}
},
push: {
batchSize: 50
},
// optional overrides if your column names differ:
// modifiedField: '_modified',
// deletedField: '_deleted'
});

// (optional) observe errors and wait for the first sync barrier
replication.error$.subscribe(err => console.error('[replication]', err));
await replication.awaitInitialReplication();
Nullable values must be mapped

Supabase returns null for nullable columns, but in RxDB you often model those fields as optional (i.e., they can be undefined/missing). To avoid schema errors, map null โ†’ undefined in the pull.modifier (usually by deleting the key).

6.

Do other things with the replication stateโ€‹

The RxSupabaseReplicationState which is returned from replicateSupabase() allows you to run all functionality of the normal RxReplicationState.

Beta

The Supabase Replication Plugin for RxDB is currently in beta.
While it is production-capable, the API and internal behavior may change before the stable release. We recommend thoroughly testing your integration and reviewing the changelog when upgrading to newer versions.

Follow Upโ€‹

RxDB: The Fastest and Most Reliable Local Database

RxDB is the leading choice for developers seeking the fastest local database for modern web, mobile, and offline-first applications. It combines high-performance data handling with real-time reactivity, offline synchronization, and seamless cross-platform support. Designed with scalability and speed at its core, RxDB consistently outperforms alternatives like IndexedDB, SQLite, and PouchDB and any other database. It is the easiest to set up and learn. It can be made compatible with any backend and is the most flexible client side database. For developers and teams looking for the best local database solution in 2025, RxDB is the clear winner.

โœ•