Offline support has been one of the most requested features for Supabase—it's the most upvoted and most commented discussion in the entire Supabase GitHub organization. For developers building mobile apps or field applications with self-hosted Supabase, the question always comes up: how do you make it work offline?
The short answer is that Supabase doesn't have built-in offline support. But that doesn't mean you're stuck. Several excellent sync layers have emerged that integrate seamlessly with both Supabase Cloud and self-hosted instances, giving you true offline-first capabilities without changing your backend.
Why Offline-First Matters
Offline-first isn't just about working without WiFi. It's about building apps that are instant and responsive, regardless of network conditions. When your app reads and writes to a local database first, every interaction feels immediate. Network sync happens in the background.
This matters especially for:
- Mobile apps where connectivity is unreliable
- Field service applications used in warehouses, construction sites, or rural areas
- Collaborative tools where users expect real-time updates
- Progressive web apps that need to work anywhere
The traditional approach—showing spinners while waiting for server responses—creates a sluggish user experience. Local-first development eliminates that friction.
The Self-Hosted Advantage
When you self-host Supabase, you gain full control over your infrastructure. This extends to offline-first architecture. You can:
- Self-host your sync service alongside Supabase
- Keep all data on your infrastructure for compliance requirements
- Customize sync behavior without vendor restrictions
- Avoid per-MAU pricing on sync services
Let's look at the main options for adding offline capabilities to your self-hosted Supabase deployment.
Option 1: PowerSync (Recommended for Most Use Cases)
PowerSync is a drop-in sync layer built specifically for Supabase. It connects to your Postgres database via logical replication (WAL) and streams changes to local SQLite databases in your app.
How PowerSync Works
- PowerSync reads your Supabase Postgres WAL
- You define Sync Rules that specify which data each client receives
- The PowerSync SDK maintains a local SQLite database
- Your app reads/writes locally—sync happens automatically
The key advantage: PowerSync doesn't require schema changes or write permissions on your Supabase database. It's truly non-invasive.
Self-Hosting PowerSync
PowerSync Open Edition can be self-hosted alongside your Supabase instance. The setup involves:
# docker-compose.yml addition
powersync:
image: journeyapps/powersync-service:latest
environment:
POWERSYNC_DATABASE_URL: postgres://postgres:password@db:5432/postgres
POWERSYNC_JWT_SECRET: your-jwt-secret
ports:
- "8080:8080"
You'll need to configure sync rules that define how data flows to clients:
# sync-rules.yaml
bucket_definitions:
user_data:
parameters: SELECT token->>'sub' as user_id FROM jwt
data:
- SELECT * FROM todos WHERE user_id = bucket.user_id
Client SDK Integration
PowerSync provides SDKs for React Native, Flutter, Swift, Kotlin, and web:
import { PowerSyncDatabase } from '@powersync/web';
import { SupabaseConnector } from './SupabaseConnector';
const db = new PowerSyncDatabase({
schema: AppSchema,
database: { dbFilename: 'app.db' }
});
const connector = new SupabaseConnector();
await db.connect(connector);
// All reads are local and instant
const todos = await db.getAll('SELECT * FROM todos');
// Writes go to local DB, sync in background
await db.execute('INSERT INTO todos (id, title) VALUES (?, ?)', [uuid(), 'New task']);
PowerSync gives you Last-Write-Wins conflict resolution by default, with options to customize behavior for complex scenarios.
Option 2: ElectricSQL (Open Source, Self-Hosted)
ElectricSQL takes a different approach. It's fully open source, self-hosted, and provides what they call "formally proven" consistency guarantees through CRDT-based sync.
Setting Up ElectricSQL
ElectricSQL runs as a service alongside your Supabase Postgres:
docker run \ -e DATABASE_URL=postgres://postgres:password@host:5432/postgres \ -e ELECTRIC_WRITE_TO_PG_MODE=direct_writes \ -p 3000:3000 \ electricsql/electric
The Electric service uses Postgres logical replication to stream changes bidirectionally.
Shape-Based Sync
ElectricSQL uses "Shapes" to define partial replication—what data syncs to each client:
import { useShape } from '@electric-sql/react'
function TodoList({ projectId }) {
const { data: todos } = useShape({
url: 'http://localhost:3000/v1/shape/todos',
where: `project_id = ${projectId}`
})
return todos.map(todo => <TodoItem key={todo.id} {...todo} />)
}
ElectricSQL's strength is its conflict-free programming model. You don't need to think about conflict resolution because the system handles it mathematically.
Trade-offs
ElectricSQL is newer and still maturing. The client SDKs are currently focused on web/TypeScript, with mobile support in development. For production mobile apps, PowerSync has more mature SDKs.
Option 3: WatermelonDB (DIY Approach)
If you prefer building your own sync logic, WatermelonDB is a reactive database for React Native that includes a sync protocol you can connect to Supabase.
The Architecture
WatermelonDB stores data in SQLite locally. You implement sync using Supabase RPC calls:
-- In Supabase, create push/pull functions CREATE OR REPLACE FUNCTION sync_push(changes jsonb) RETURNS void AS $$ BEGIN -- Process incoming changes -- Handle conflicts -- Update timestamps END; $$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION sync_pull(last_pulled_at timestamp) RETURNS jsonb AS $$ BEGIN -- Return all changes since last sync END; $$ LANGUAGE plpgsql;
Your app calls these via Supabase client:
const { data } = await supabase.rpc('sync_pull', {
last_pulled_at: lastSync
});
When to Use WatermelonDB
This approach makes sense when:
- You need complete control over sync logic
- You're already using WatermelonDB
- Your sync patterns are simple (basic CRUD, last-write-wins)
- You want to avoid additional infrastructure
The downside: you're responsible for building and maintaining the sync logic, handling edge cases, and dealing with conflicts.
Option 4: RxDB (Cross-Platform JavaScript)
RxDB provides a Supabase replication plugin that handles sync automatically. It's a good choice for web apps and can work with React Native.
import { replicateSupabase } from 'rxdb/plugins/replication-supabase';
const replicationState = replicateSupabase({
collection: myCollection,
supabaseClient: supabase,
table: 'todos',
pull: {},
push: {}
});
RxDB handles the replication protocol, using PostgREST for sync and Supabase Realtime for live updates.
Choosing the Right Solution
Here's a practical decision framework:
| Scenario | Recommended Solution |
|---|---|
| Mobile app (React Native/Flutter) with complex sync | PowerSync |
| Web app with real-time collaboration | ElectricSQL |
| Simple sync, existing WatermelonDB | Keep WatermelonDB |
| JavaScript/TypeScript cross-platform | RxDB |
| Maximum control, simple patterns | Build custom with Supabase RPC |
For most teams deploying self-hosted Supabase, PowerSync offers the best balance of features, maturity, and ease of integration.
Managing Multiple Projects with Offline-First
If you're running multiple Supabase projects—perhaps one per customer or environment—you'll need to manage multiple sync service instances too. This is where infrastructure complexity grows.
Supascale simplifies managing multiple self-hosted Supabase projects with features like automated backups and one-click deployments. While offline sync requires additional services like PowerSync, having solid infrastructure management for your Supabase instances is the foundation.
Practical Considerations
Storage and Performance
Local SQLite databases grow with synced data. Plan for:
- Selective sync: Only sync what users need
- Pagination: Use PowerSync buckets or Electric shapes to limit data
- Cleanup: Implement data retention policies client-side
Conflict Resolution
Most apps do fine with Last-Write-Wins. But if you're building collaborative tools:
- Document your conflict strategy
- Consider operational transforms for text
- Test edge cases: simultaneous edits, offline for days, etc.
Security
Even with offline-first, security rules still apply:
- Row Level Security protects data at the source
- Sync rules (PowerSync) or shapes (Electric) filter what reaches clients
- Local data should be encrypted on device
Conclusion
Offline-first isn't built into Supabase, but the ecosystem has excellent solutions. For self-hosted deployments, you can run the entire stack—Supabase, sync service, everything—on your own infrastructure.
PowerSync and ElectricSQL are the mature options. PowerSync is battle-tested for mobile apps; ElectricSQL offers a compelling open-source, CRDT-based approach. Both integrate cleanly with self-hosted Supabase.
The combination of self-hosted Supabase and a proper sync layer gives you the best of both worlds: Postgres reliability, full data control, and apps that work instantly whether online or off.
