Postgres<>SQLite sync layer

Build local-first apps with simple state management and real-time reactivity.

Supports these frameworks

Diagram providing overview of PowerSync service and SDK.

WHAT DEVELOPERS ARE SAYING

Tyler Shukert
Supabase
“PowerSync is really bulletproof — every single kind of interaction that you might have with the data, it's got covered.”
Tyler Shukert
Supabase
Sebastian Röhl
HabitKit
“Integrated @powersync_ into an experimental branch of @HabitKit. Setup was pretty easy and fast. Really love the developer experience and I'm amazed how good it works 🤩”
Sebastian Röhl
HabitKit
Praful Mathur
Sarama / 5 Gyres volunteer
“It was incredible to work with PowerSync. Probably the easiest thing that we had to do in terms of development.”
Praful Mathur
Sarama / 5 Gyres volunteer
Moe Amaya
Monograph
“Been super pumped getting Powersync to work with Next and Supabase. I have a CRM project app syncing and persisting after 6hrs of coding. Never been happier on an engineering project!"
Praful Mathur
Sarama / 5 Gyres volunteer
Marco Napoli
Jedi Pixels
"I have a project that I am currently working and testing with @powersync_. It's working flawlessly..."
Marco Napoli
Jedi Pixels
Simon Grimm
Galaxies.dev
"PowerSync makes it easy to set up local-first without messing up the architecture of the rest of your system."
Simon Grimm
Galaxies.dev
Carl Kritzinger
Intuitably
"As a developer, this feels like a game-changer: I get incredibly robust data sync with LESS code."
Carl Kritzinger
Intuiably
Ev Haus
ZenHub
"It's been a long-time dream of mine to eliminate all client state management with queries to a local DB. This is probably the closest thing I've seen to that vision."
Carl Kritzinger
Intuiably
loosely-coupled architecture

Connects to any Postgres in a non-invasive way

PowerSync reads data from the Postgres WAL.

  • No schema changes or other modifications required.
  • No custom extensions required.
  • No write permission required.
Installation guide
Diagram illustrating Postgres database backend publishing to PowerSync sync layer.
Dynamic Partial replication

Control which data is synced with which users

Define sync rules to control which buckets of data should sync to which users, using SQL statements.

Data replicated from Postgres dynamically hydrates the data buckets on the PowerSync service, which are then streamed to relevant users’ local SQLite databases in real-time.

Explore the docs
Diagram illustrating dynamic partitioning of data to different app users with sync rules.
bucket_definitions:
	
  # define your various buckets (specify any unique identifier for each)
  user_lists_and_todos:
        
    # parameter query: get parameters from the JWT token (e.g. user ID) and/or retrieve
    # additional parameters from elsewhere in your database to use in your data queries
    parameters: SELECT token_parameters.user_id as user_id
        
    # data queries: using the aforementioned parameters, query the data to be synced.
    # in this to-do list example, we are querying lists and todos that belong to the user
    data:
    - SELECT * FROM lists WHERE owner_id = bucket.user_id
    - SELECT * FROM todos WHERE created_by = bucket.user_id
QUERIES WITH near-ZERO LATENCY

Work with a local SQLite database that syncs automatically

Read and write to a single SQLite database embedded in the client SDK.

  • No need for API requests and caching to read data.
  • Run live queries against the local database.
  • Query with raw SQL or through an ORM.
Learn more
Diagram illustrating local SQLite database sync.
// Local queries
await db.execute('SELECT * FROM lists WHERE id = ?', [id]);

// Local writes
await db.execute('INSERT INTO tasks(id, name) VALUES(uuid(), ?)', [name]);
await db.execute('UPDATE tasks SET completed = NOT completed WHERE id = ?', [task.id]);

// Live query — enables reactivity whenever results update
const results = usePowerSyncWatchedQuery('SELECT name, completed FROM tasks WHERE list_id = ?', [list_id]);
// Local queries
await db.execute('SELECT * FROM lists WHERE id = ?', [id]);

// Local writes
await db.execute('INSERT INTO tasks(id, name) VALUES(uuid(), ?)', [name]);
await db.execute('UPDATE tasks SET completed = NOT completed WHERE id = ?', [task.id]);

// Live query — enables reactivity whenever results update
db.watch('SELECT name, completed FROM tasks WHERE list_id = ?', [list_id]).map((results) { });
// Insert using Kysely query builder
await db.insertInto('users').values({ id: '1', name: 'John' }).execute();

// Query using Kysely query builder
const result = await db.selectFrom('users').selectAll().execute();
// Insert using Drift ORM
await appdb
	.into(appdb.todoItems)
	.insert(TodoItemsCompanion.insert(description: 'Test'));
    
// Watch a query on the Drift database
appdb.select(appdb.todoItems).watch().listen((todos) {
  print('Todos: $todos');
});
Diagram illustrating local SQLite database sync.
// Local queries
await db.execute('SELECT * FROM lists WHERE id = ?', [id]);

// Local writes
await db.execute('INSERT INTO tasks(id, name) VALUES(uuid(), ?)', [name]);
await db.execute('UPDATE tasks SET completed = NOT completed WHERE id = ?', [task.id]);

// Live query — enables reactivity whenever results update
const results = usePowerSyncWatchedQuery('SELECT name, completed FROM tasks WHERE list_id = ?', [list_id]);
// Local queries
await db.execute('SELECT * FROM lists WHERE id = ?', [id]);

// Local writes
await db.execute('INSERT INTO tasks(id, name) VALUES(uuid(), ?)', [name]);
await db.execute('UPDATE tasks SET completed = NOT completed WHERE id = ?', [task.id]);

// Live query — enables reactivity whenever results update
db.watch('SELECT name, completed FROM tasks WHERE list_id = ?', [list_id]).map((results) { });
// Insert using Kysely query builder
await db.insertInto('users').values({ id: '1', name: 'John' }).execute();

// Query using Kysely query builder
const result = await db.selectFrom('users').selectAll().execute();
// Insert using Drift ORM
await appdb
	.into(appdb.todoItems)
	.insert(TodoItemsCompanion.insert(description: 'Test'));
    
// Watch a query on the Drift database
appdb.select(appdb.todoItems).watch().listen((todos) {
	print('Todos: $todos');
});
Custom WRITE LOgic

Control how writes are applied to Postgres, using your backend

Writes go through your backend, allowing you to apply any business logic, validations, authorization and conflict resolution strategy (including using CRDT data structures for fine-grained collaboration).

PowerSync treats the server as authoritative and automatically ensures all clients converge to the server state with causal consistency.

Learn more
Diagram illustrating custom write logic and conflict resolution strategies.
SEE How it works

Try the live demo

Visit this website on your desktop to see a demo
LOCAL-FIRST DEVELOPMENT

Why local-first architecture?

Local-first apps work with a local database and sync data in the background.

Real-time data streaming

Instantly reactive UI

Always available, works offline

Multi-user collaboration

Control data privacy

Start local-only or local-first

POWERSYNC COMMUNITY

Join the community

How the PowerSync team approaches community and supports us is really outstanding.
I've simply never experienced such support.
Ben M.
Octologs
Join our Discord server

Ask questions and join in on discussions about local-first development.

Future support

Future support for other databases, including Oracle, MySQL and MS SQL Server, is planned.

Please submit your ideas for frameworks and features that we should consider.

See the roadmap
Talk to a PowerSync engineer

Schedule time with us.

Schedule a call