> ## Documentation Index
> Fetch the complete documentation index at: https://docs.powersync.com/llms.txt
> Use this file to discover all available pages before exploring further.
# PowerSync Setup Guide
> This guide walks you through adding PowerSync to your app project step-by-step.
# 1. Configure Your Source Database
PowerSync needs to connect to your source database (Postgres, MongoDB, MySQL or SQL Server) to replicate data. Before setting up PowerSync, you need to configure your database with the appropriate permissions and replication settings.
Configuring Postgres for PowerSync involves three main tasks:
1. **Enable logical replication**: PowerSync reads the Postgres WAL using logical replication. Set `wal_level = logical` in your Postgres configuration.
2. **Create a PowerSync database user**: Create a role with replication privileges and read-only access to your tables.
3. **Create a `powersync` publication**: Create a logical replication publication named `powersync` to specify which tables to replicate.
```sql General theme={null}
-- 1. Enable logical replication (requires restart)
ALTER SYSTEM SET wal_level = logical;
-- 2. Create PowerSync database user/role with replication privileges and read-only access to your tables
CREATE ROLE powersync_role WITH REPLICATION BYPASSRLS LOGIN PASSWORD 'myhighlyrandompassword';
-- Set up permissions for the newly created role
-- Read-only (SELECT) access is required
GRANT SELECT ON ALL TABLES IN SCHEMA public TO powersync_role;
-- Optionally, grant SELECT on all future tables (to cater for schema additions)
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO powersync_role;
-- 3. Create a publication to replicate tables. The publication must be named "powersync"
CREATE PUBLICATION powersync FOR ALL TABLES;
```
```sql Supabase theme={null}
-- Supabase has logical replication enabled by default
-- Just create the user and publication:
-- Create PowerSync database user/role with replication privileges and read-only access to your tables
CREATE ROLE powersync_role WITH REPLICATION BYPASSRLS LOGIN PASSWORD 'myhighlyrandompassword';
-- Set up permissions for the newly created role
-- Read-only (SELECT) access is required
GRANT SELECT ON ALL TABLES IN SCHEMA public TO powersync_role;
-- Optionally, grant SELECT on all future tables (to cater for schema additions)
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO powersync_role;
-- Create a publication to replicate tables. The publication must be named "powersync"
CREATE PUBLICATION powersync FOR ALL TABLES;
```
```bash Docker (Self-hosting) theme={null}
# 1. Create a Docker network (if not already created)
# This allows various PowerSync containers to communicate with each other
docker network create powersync-network
# 2. Run Postgres source database with logical replication enabled (required for PowerSync)
docker run -d \
--name powersync-postgres \
--network powersync-network \
-e POSTGRES_PASSWORD="my_secure_password" \
-p 5432:5432 \
postgres:18 \
postgres -c wal_level=logical
# 3. Configure PowerSync user and publication
# This creates a PowerSync database user/role with replication privileges and read-only access to your tables
# Read-only (SELECT) access is also granted to all future tables (to cater for schema additions)
# It also creates a publication to replicate tables. The publication must be named "powersync"
docker exec -it powersync-postgres psql -U postgres -c "
CREATE ROLE powersync_role WITH REPLICATION BYPASSRLS LOGIN PASSWORD 'myhighlyrandompassword';
GRANT SELECT ON ALL TABLES IN SCHEMA public TO powersync_role;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO powersync_role;
CREATE PUBLICATION powersync FOR ALL TABLES;"
```
* **Version compatibility**: PowerSync requires Postgres version 11 or greater.
**Learn More**
* For more details on Postgres setup, including provider-specific guides (Supabase, AWS RDS, etc.), see [Source Database Setup](/configuration/source-db/setup#postgres).
* **Self-hosting PowerSync?** See the [Self-Host-Demo App](https://github.com/powersync-ja/self-host-demo/tree/main/demos/nodejs) for a complete working example of connecting a Postgres source database to PowerSync.
For MongoDB Atlas databases, the minimum permissions when using built-in roles are:
```
read@
readWrite@._powersync_checkpoints
```
To allow PowerSync to automatically enable `changeStreamPreAndPostImages` on replicated collections (optional, but recommended), additionally add:
```
dbAdmin@
```
**Version compatibility**: PowerSync requires MongoDB version 6.0 or greater.
**Learn More**
* For more details including instructions for self-hosted MongoDB, or for custom roles on MongoDB Atlas, see [Source Database Setup](/configuration/source-db/setup#mongodb).
* **Self-hosting PowerSync?** See the [Self-Host-Demo App](https://github.com/powersync-ja/self-host-demo/tree/main/demos/nodejs-mongodb) for a complete working example of connecting a MongoDB source database to PowerSync.
For MySQL, you need to configure binary logging and create a user with replication privileges:
```sql theme={null}
-- Configure binary logging
-- Add to MySQL option file (my.cnf or my.ini):
server_id=
log_bin=ON
enforce_gtid_consistency=ON
gtid_mode=ON
binlog_format=ROW
-- Create a user with necessary privileges
CREATE USER 'repl_user'@'%' IDENTIFIED BY '';
-- Grant replication client privilege
GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'repl_user'@'%';
-- Grant select access to the specific database
GRANT SELECT ON .* TO 'repl_user'@'%';
-- Apply changes
FLUSH PRIVILEGES;
```
**Version compatibility**: PowerSync requires MySQL version 5.7 or greater.
**Learn More**
* For more details on MySQL setup, see [Source Database Setup](/configuration/source-db/setup#mysql-beta).
* **Self-hosting PowerSync?** See the [Self-Host-Demo App](https://github.com/powersync-ja/self-host-demo/tree/main/demos/nodejs-mysql) for a complete working example of connecting a MySQL source database to PowerSync.
Refer to [these instructions](/configuration/source-db/setup#sql-server-alpha).
**Self-hosting PowerSync?** See the [Self-Host-Demo App](https://github.com/powersync-ja/self-host-demo/tree/main/demos/nodejs-mssql) for a complete working example of connecting a SQL Server source database to PowerSync.
# 2. Set Up PowerSync Service Instance
PowerSync is available as a cloud-hosted service (PowerSync Cloud) or can be self-hosted (PowerSync Open Edition or PowerSync Enterprise Self-Hosted Edition).
If you haven't yet, sign up for a free PowerSync Cloud account [here](https://accounts.journeyapps.com/portal/powersync-signup?s=docs).
After signing up, you will be taken to the [PowerSync Dashboard](https://dashboard.powersync.com/).
Here, create a new project. *Development* and *Production* instances of the PowerSync Service will be created by default in the project.
Self-hosted PowerSync runs via Docker.
Below is a minimal example of setting up the PowerSync Service with Postgres as the bucket storage database and example Sync Rules. MongoDB is also supported as a bucket storage database (docs are linked at the end of this step), and you will learn more about Sync Rules in a next step.
```bash theme={null}
# 1. Create a directory for your config
mkdir powersync-service && cd powersync-service
# 2. Set up bucket storage (Postgres and MongoDB are supported)
docker run -d \
--name powersync-postgres-storage \
--network powersync-network \
-p 5433:5432 \
-e POSTGRES_PASSWORD="my_secure_storage_password" \
-e POSTGRES_DB=powersync_storage \
postgres:18
## Set up Postgres storage user
docker exec -it powersync-postgres-storage psql -U postgres -d powersync_storage -c "
CREATE USER powersync_storage_user WITH PASSWORD 'my_secure_user_password';
GRANT CREATE ON DATABASE powersync_storage TO powersync_storage_user;"
# 3. Create config.yaml (see below)
# 4. Run PowerSync Service
# The Service config can be specified as an environment variable (shown below), as a filepath, or as a command line parameter
# See these docs for more details: https://docs.powersync.com/configuration/powersync-service/self-hosted-instances
docker run -d \
--name powersync \
--network powersync-network \
-p 8080:8080 \
-e POWERSYNC_CONFIG_B64="$(base64 -i ./config.yaml)" \
journeyapps/powersync-service:latest
```
**Basic `config.yaml` structure:**
```yaml theme={null}
# Source database connection (see the next step for more details)
replication:
connections:
- type: postgresql # or mongodb, mysql, mssql
uri: postgresql://powersync_role:myhighlyrandompassword@powersync-postgres:5432/postgres
sslmode: disable # Only for local/private networks
# Connection settings for bucket storage (Postgres and MongoDB are supported)
storage:
type: postgresql
uri: postgresql://powersync_storage_user:my_secure_user_password@powersync-postgres-storage:5432/powersync_storage
sslmode: disable # Use 'disable' only for local/private networks
# Sync Rules (defined in a later step)
sync_rules:
content: |
bucket_definitions:
global:
data:
- SELECT * FROM lists
- SELECT * FROM todos
```
**Note**: This example assumes you've configured your source database with the required user and publication (see the previous step)
and are running it via Docker in the 'powersync-network' network.
If you are not using Docker, you will need to specify the connection details in the `config.yaml` file manually (see next step for more details).
**Learn More**
* [Self-Hosting Introduction](/intro/self-hosting)
* [Self-Host Demo App](https://github.com/powersync-ja/self-host-demo) for complete working examples.
* [Self-Hosted Service Configuration](/configuration/powersync-service/self-hosted-instances) for more details on the config file structure.
# 3. Connect PowerSync to Your Source Database
The next step is to connect your PowerSync Service instance to your source database.
In the [PowerSync Dashboard](https://dashboard.powersync.com/), select your project and instance, then go to **Database Connections**:
1. Click **Connect to Source Database**
2. Select the appropriate database type tab (Postgres, MongoDB, MySQL or SQL Server)
3. Fill in your connection details:
**Note**: Use the username (e.g., `powersync_role`) and password you created in Step 1: Configure your Source Database.
* **Postgres**: Host, Port (5432), Database name, Username, Password, SSL Mode
* **MongoDB**: Connection URI (e.g., `mongodb+srv://user:pass@cluster.mongodb.net/database`)
* **MySQL**: Host, Port (3306), Database name, Username, Password
* **SQL Server**: Name, Host, Port (1433), Database name, Username, Password
4. Click **Test Connection** to verify
5. Click **Save Connection**
PowerSync will now deploy and configure an isolated cloud environment, which can take a few minutes.
**Learn More**
For more details on database connections, including provider-specific connection details (Supabase, AWS RDS, MongoDB Atlas, etc.), see [Source Database Connection](/configuration/source-db/connection).
For self-hosted setups, configure the source database connection in your `config.yaml` file (as you did in the previous step). Examples for the different database types are below.
**Note**: Use the username (e.g., `powersync_role`) and password you created in Step 1: Configure your Source Database.
```yaml Postgres theme={null}
replication:
connections:
- type: postgresql # or mongodb, mysql, mssql
uri: postgresql://powersync_role:myhighlyrandompassword@powersync-postgres:5432/postgres # The connection URI or individual parameters can be specified.
sslmode: disable # 'verify-full' (default) or 'verify-ca' or 'disable'
# Note: 'disable' is only suitable for local/private networks, not for public networks
```
```yaml MongoDB theme={null}
replication:
connections:
- type: mongodb
uri: mongodb+srv://user:password@cluster.mongodb.net/database
post_images: auto_configure
```
```yaml MySQL theme={null}
replication:
connections:
- type: mysql
uri: mysql://repl_user:password@host:3306/database
```
```yaml SQL Server theme={null}
replication:
connections:
- type: mssql
uri: mssql://user:password@$host:1433/database
schema: dbo
additionalConfig:
trustServerCertificate: true
pollingIntervalMs: 1000
pollingBatchSize: 20
```
**Learn More**
See the [self-host-demo app](https://github.com/powersync-ja/self-host-demo) for complete working examples of the different database types.
# 4. Define Basic Sync Rules
Sync Rules control which data gets synced to which users/devices. They consist of SQL-like queries organized into "buckets" (groupings of data). Each PowerSync Service instance has a Sync Rules definition in YAML format.
We recommend starting with a simple **global bucket** that syncs data to all users. This is the simplest way to get started.
```yaml Postgres Example theme={null}
bucket_definitions:
global:
data:
- SELECT * FROM todos
- SELECT * FROM lists WHERE archived = false
```
```yaml MongoDB Example theme={null}
bucket_definitions:
global:
data:
# Note that MongoDB uses “_id” as the name of the ID field in collections whereas
# PowerSync uses “id” in its client-side database. This is why the below syntax
# should always be used in the data queries when pairing PowerSync with MongoDB.
- SELECT _id as id, * FROM lists
- SELECT _id as id, * FROM todos WHERE archived = false
```
```yaml MySQL Example theme={null}
bucket_definitions:
global:
data:
- SELECT * FROM todos
- SELECT * FROM lists WHERE archived = 0
```
```yaml SQL Server Example theme={null}
bucket_definitions:
global:
data:
- SELECT * FROM todos
- SELECT * FROM lists WHERE archived = 0
```
### Deploy Sync Rules
In the [PowerSync Dashboard](https://dashboard.powersync.com/):
1. Select your project and instance
2. Go to the **Sync Rules** view
3. Edit the YAML directly in the dashboard
4. Click **Deploy** to validate and deploy your Sync Rules
Add to your `config.yaml`:
```yaml theme={null}
sync_rules:
content: |
bucket_definitions:
global:
data:
- SELECT * FROM todos
- SELECT * FROM lists WHERE archived = false
```
**Note**: Table/collection names within your Sync Rules must match the table names defined in your client-side schema (defined in a later step below).
**Learn More**
For more details on Sync Rules usage, see the [Sync Rules documentation](/sync/rules/overview).
# 5. Generate a Development Token
For quick development and testing, you can generate a temporary development token instead of implementing full authentication.
You'll use this token for two purposes:
* **Testing with the *Sync Diagnostics Client*** (in the next step) to verify your setup and Sync Rules
* **Connecting your app** (in a later step) to test the client SDK integration
1. In the [PowerSync Dashboard](https://dashboard.powersync.com/), select your project and instance
2. Go to the **Client Auth** view
3. Check the **Development tokens** setting and save your changes
4. Click the **Connect** button in the top bar
5. **Enter token subject**: Since you're starting with just a simple global bucket in your Sync Rules that syncs all data to all users (as we recommended in the previous step), you can just put something like `test-user` as the token subject (which would normally be the user ID you want to test with).
6. Click **Generate token** and copy the token
Development tokens expire after 12 hours.
For self-hosted setups, you can generate development tokens using the [powersync-service test-client](https://github.com/powersync-ja/powersync-service/tree/main/test-client):
Generate a temporary private/public key-pair (RS256) or shared key (HS256) for JWT signing and verification.
Use an online JWK generator like [mkjwk.org](https://mkjwk.org/) (select RSA, 2048 bits, Signature use, RS256 algorithm).
Or generate locally with Node.js:
```bash theme={null}
# Install pem-jwk if needed
npm install -g pem-jwk
# Generate private key
openssl genrsa -out private-key.pem 2048
# Convert public key to JWK format
openssl rsa -in private-key.pem -pubout | pem-jwk
```
Use an online JWK generator like [mkjwk.org](https://mkjwk.org/) (select oct, 256 bits, Signature use, HS256 algorithm) - this outputs base64url directly.
Or generate and convert using OpenSSL:
```bash theme={null}
# Generate and convert to base64url
openssl rand -base64 32 | tr '+/' '-_' | tr -d '='
```
For production environments, shared secrets (HS256) are not recommended.
Add the `client_auth` parameter to your `config.yaml`:
Copy the JWK values from mkjwk.org or the pem-jwk output, then add to your config:
```yaml config.yaml theme={null}
# Client (application end user) authentication settings
client_auth:
# static collection of public keys for JWT verification
jwks:
keys:
- kty: 'RSA'
n: '[rsa-modulus]'
e: '[rsa-exponent]'
alg: 'RS256'
kid: 'dev-key-1'
```
Copy the `k` value from mkjwk.org or the OpenSSL output, then add to your config:
```yaml config.yaml theme={null}
# Client (application end user) authentication settings
client_auth:
audience: ['http://localhost:8080', 'http://127.0.0.1:8080']
# static collection of public keys for JWT verification
jwks:
keys:
- kty: oct
alg: 'HS256'
k: '[base64url-encoded-shared-secret]'
kid: 'dev-key-1'
```
These examples use static `jwks: keys:` for simplicity. For production, we recommend using `jwks_uri` to point to a JWKS endpoint instead. See [Custom Authentication](/configuration/auth/custom) for more details.
1. If you have not done so already, clone the [`powersync-service` repo](https://github.com/powersync-ja/powersync-service/tree/main)
2. Install the dependencies:
* In the project root, run the following commands:
```bash theme={null}
pnpm install
pnpm build
```
* In the `test-client` directory, run the following commands:
```bash theme={null}
pnpm build
```
3. Generate a new token by running the following command in the `test-client` directory with your updated `config.yaml` file:
```bash theme={null}
node dist/bin.js generate-token --config path/to/config.yaml --sub test-user
```
Replace `test-user` with the user ID you want to authenticate:
* If you're using **global Sync Rules**, you can use any value (e.g., `test-user`) since all data syncs to all users
* If you're using **user-specific Sync Rules**, use a user ID that matches a user in your database (this will be used as `request.user_id()` in your Sync Rules)
Development tokens expire after 12 hours.
# 6. \[Optional] Test Sync with the Sync Diagnostics Client
Before implementing the PowerSync Client SDK in your app, you can validate that syncing is working correctly using our [Sync Diagnostics Client](https://diagnostics-app.powersync.com) (this hosted version works with both PowerSync Cloud and self-hosted setups).
Use the development token you generated in the [previous step](#5-generate-a-development-token) to connect and verify your setup:
1. Go to [https://diagnostics-app.powersync.com](https://diagnostics-app.powersync.com)
2. Enter your development token (from the [Generate a Development Token](#5-generate-a-development-token) step above)
3. Enter your PowerSync instance URL (found in [PowerSync Dashboard](https://dashboard.powersync.com/) - click **Connect** in the top bar)
4. Click **Connect**
1. Go to [https://diagnostics-app.powersync.com](https://diagnostics-app.powersync.com)
2. Enter your development token (from the [Generate a Development Token](#5-generate-a-development-token) step above)
3. Enter your PowerSync Service endpoint (the URL where your self-hosted service is running, e.g. `http://localhost:8080` if running locally)
4. Click **Connect**
The Sync Diagnostics Client can also be run as a local standalone web app — see the [README](https://github.com/powersync-ja/powersync-js/tree/main/tools/diagnostics-app#readme) for instructions.
The Sync Diagnostics Client will connect to your PowerSync Service instance and display [information](https://github.com/powersync-ja/powersync-js/tree/main/tools/diagnostics-app#functionality) about the synced data, and allow you to [query](https://github.com/powersync-ja/powersync-js/tree/main/tools/diagnostics-app#sql-console) the client-side SQLite database.
**Checkpoint:**
Inspect your global bucket and synced tables in the Sync Diagnostics Client — these should match the Sync Rules you [defined previously](#4-define-basic-sync-rules). This confirms your setup is working correctly before integrating the client SDK into your app.
# 7. Use the Client SDK
Now it's time to integrate PowerSync into your app. This involves installing the SDK, defining your client-side schema, instantiating the database, connecting to your PowerSync Service instance, and reading/writing data.
### Install the Client SDK
Add the PowerSync Client SDK to your app project. PowerSync provides SDKs for various platforms and frameworks.
Add the [PowerSync React Native NPM package](https://www.npmjs.com/package/@powersync/react-native) to your project:
```bash theme={null}
npx expo install @powersync/react-native
```
```bash theme={null}
yarn expo add @powersync/react-native
```
```
pnpm expo install @powersync/react-native
```
**Install peer dependencies**
PowerSync requires a SQLite database adapter. Choose between:
[PowerSync OP-SQLite](https://www.npmjs.com/package/@powersync/op-sqlite) offers:
* Built-in encryption support via SQLCipher
* Smoother transition to React Native's New Architecture
```bash theme={null}
npx expo install @powersync/op-sqlite @op-engineering/op-sqlite
```
```bash theme={null}
yarn expo add @powersync/op-sqlite @op-engineering/op-sqlite
```
```
pnpm expo install @powersync/op-sqlite @op-engineering/op-sqlite
```
The [@journeyapps/react-native-quick-sqlite](https://www.npmjs.com/package/@journeyapps/react-native-quick-sqlite) package is the original database adapter for React Native and therefore more battle-tested in production environments.
```bash theme={null}
npx expo install @journeyapps/react-native-quick-sqlite
```
```bash theme={null}
yarn expo add @journeyapps/react-native-quick-sqlite
```
```
pnpm expo install @journeyapps/react-native-quick-sqlite
```
**iOS with `use_frameworks!`**
If your iOS project uses `use_frameworks!`, add the `react-native-quick-sqlite` plugin to your app.json or app.config.js and configure the staticLibrary option:
```
{
"expo": {
"plugins": [
[
"@journeyapps/react-native-quick-sqlite",
{
"staticLibrary": true
}
]
]
}
}
```
This plugin automatically configures the necessary build settings for `react-native-quick-sqlite` to work with `use_frameworks!`.
**Using Expo Go?** Our native database adapters listed below (OP-SQLite and React Native Quick SQLite) are not compatible with Expo Go's sandbox environment. To run PowerSync with Expo Go install our JavaScript-based adapter `@powersync/adapter-sql-js` instead. See details [here](/client-sdks/frameworks/expo-go-support).
**Polyfills and additional notes:**
* For async iterator support with watched queries, additional polyfills are required. See the [Babel plugins section](https://www.npmjs.com/package/@powersync/react-native#babel-plugins-watched-queries) in the README.
* When using the **OP-SQLite** package, we recommend adding this [metro config](https://github.com/powersync-ja/powersync-js/tree/main/packages/react-native#metro-config-optional)
to avoid build issues.
Add the [PowerSync Web NPM package](https://www.npmjs.com/package/@powersync/web) to your project:
```bash theme={null}
npm install @powersync/web
```
```bash theme={null}
yarn add @powersync/web
```
```bash theme={null}
pnpm install @powersync/web
```
**Required peer dependencies**
This SDK currently requires [`@journeyapps/wa-sqlite`](https://www.npmjs.com/package/@journeyapps/wa-sqlite) as a peer dependency. Install it in your app with:
```bash theme={null}
npm install @journeyapps/wa-sqlite
```
```bash theme={null}
yarn add @journeyapps/wa-sqlite
```
```bash theme={null}
pnpm install @journeyapps/wa-sqlite
```
Add the [PowerSync Node NPM package](https://www.npmjs.com/package/@powersync/node) to your project:
```bash theme={null}
npm install @powersync/node
```
```bash theme={null}
yarn add @powersync/node
```
```bash theme={null}
pnpm install @powersync/node
```
**Peer dependencies**
The PowerSync SDK for Node.js supports multiple drivers. More details are available under [encryption and custom drivers](/client-sdks/reference/node#encryption-and-custom-sqlite-drivers),
we currently recommend the `better-sqlite3` package for most users:
```bash theme={null}
npm install better-sqlite3
```
```bash theme={null}
yarn add better-sqlite3
```
```bash theme={null}
pnpm install better-sqlite3
```
Previous versions of the PowerSync SDK for Node.js used the `@powersync/better-sqlite3` fork as a
required peer dependency.
This is no longer recommended. After upgrading to `@powersync/node` version `0.12.0` or later, ensure
the old package is no longer installed by running `@powersync/better-sqlite3`.
**Common installation issues**
The `better-sqlite` package requires native compilation, which depends on certain system tools.
Prebuilt assets are available and used by default, but a custom compilation may be started depending on the Node.js
or Electron version used.
This compilation process is handled by `node-gyp` and may fail if required dependencies are missing or misconfigured.
Refer to the [PowerSync Node package README](https://www.npmjs.com/package/@powersync/node) for more details.
Add the [PowerSync Capacitor NPM package](https://www.npmjs.com/package/@powersync/capacitor) to your project:
```bash theme={null}
npm install @powersync/capacitor
```
```bash theme={null}
yarn add @powersync/capacitor
```
```bash theme={null}
pnpm install @powersync/capacitor
```
**Install Peer Dependencies**
You must also install the following peer dependencies:
```bash theme={null}
npm install @capacitor-community/sqlite @powersync/web @journeyapps/wa-sqlite
```
```bash theme={null}
yarn add @capacitor-community/sqlite @powersync/web @journeyapps/wa-sqlite
```
```bash theme={null}
pnpm install @capacitor-community/sqlite @powersync/web @journeyapps/wa-sqlite
```
After installing, sync your Capacitor project:
```bash theme={null}
npx cap sync
```
Add the [PowerSync pub.dev package](https://pub.dev/packages/powersync) to your project:
```bash theme={null}
flutter pub add powersync
```
Add the [PowerSync SDK](https://central.sonatype.com/artifact/com.powersync/core) to your project by adding the following to your `build.gradle.kts` file:
```gradle theme={null}
kotlin {
//...
sourceSets {
commonMain.dependencies {
implementation("com.powersync:core:$powersyncVersion")
// If you want to use the Supabase Connector, also add the following:
implementation("com.powersync:connector-supabase:$powersyncVersion")
}
//...
}
}
```
**CocoaPods configuration (recommended for iOS)**
Add the following to the `cocoapods` config in your `build.gradle.kts`:
```gradle theme={null}
cocoapods {
//...
pod("powersync-sqlite-core") {
linkOnly = true
}
framework {
isStatic = true
export("com.powersync:core")
}
//...
}
```
The `linkOnly = true` attribute and `isStatic = true` framework setting ensure that the `powersync-sqlite-core` binaries are statically linked.
You can add the PowerSync Swift package to your project using either `Package.swift` or Xcode:
```swift theme={null}
let package = Package(
//...
dependencies: [
//...
.package(
url: "https://github.com/powersync-ja/powersync-swift",
exact: ""
),
],
targets: [
.target(
name: "YourTargetName",
dependencies: [
.product(
name: "PowerSync",
package: "powersync-swift"
)
]
)
]
)
```
1. Follow [this guide](https://developer.apple.com/documentation/xcode/adding-package-dependencies-to-your-app#Add-a-package-dependency) to add a package to your project.
2. Use `https://github.com/powersync-ja/powersync-swift.git` as the URL
3. Include the exact version (e.g., `1.0.x`)
For desktop/server/binary use-cases and WPF, add the [`PowerSync.Common`](https://www.nuget.org/packages/PowerSync.Common/) NuGet package to your project:
```bash theme={null}
dotnet add package PowerSync.Common --prerelease
```
For MAUI apps, add both [`PowerSync.Common`](https://www.nuget.org/packages/PowerSync.Common/) and [`PowerSync.Maui`](https://www.nuget.org/packages/PowerSync.Maui/) NuGet packages to your project:
```bash theme={null}
dotnet add package PowerSync.Common --prerelease
dotnet add package PowerSync.Maui --prerelease
```
Add `--prerelease` while this package is in alpha. To install a specific version, use `--version` instead: `dotnet add package PowerSync.Common --version 0.0.6-alpha.1`
Add the [PowerSync SDK](https://central.sonatype.com/artifact/com.powersync/core) to your project by adding the following to your `Cargo.toml` file:
```shell theme={null}
cargo add powersync
```
### Define Your Client-Side Schema
This refers to the schema for the managed SQLite database exposed by the PowerSync Client SDKs, that your app can read from and write to. The schema is applied when the database is instantiated (as we'll show in the next step) — no migrations are required.
*PowerSync Cloud:* The easiest way to generate your schema is using the [PowerSync Dashboard](https://dashboard.powersync.com/). Click the **Connect** button in the top bar to generate the client-side schema based on your Sync Rules in your preferred language.
Here's an example schema for a simple `todos` table:
```typescript React Native (TS) theme={null}
import { column, Schema, Table } from '@powersync/react-native';
const todos = new Table(
{
list_id: column.text,
created_at: column.text,
completed_at: column.text,
description: column.text,
created_by: column.text,
completed_by: column.text,
completed: column.integer
},
{ indexes: { list: ['list_id'] } }
);
export const AppSchema = new Schema({
todos
});
```
```typescript Web & Capacitor (TS) theme={null}
import { column, Schema, Table } from '@powersync/web';
const todos = new Table(
{
list_id: column.text,
created_at: column.text,
completed_at: column.text,
description: column.text,
created_by: column.text,
completed_by: column.text,
completed: column.integer
},
{ indexes: { list: ['list_id'] } }
);
export const AppSchema = new Schema({
todos
});
```
```typescript Node.js (TS) theme={null}
import { column, Schema, Table } from '@powersync/node';
const todos = new Table(
{
list_id: column.text,
created_at: column.text,
completed_at: column.text,
description: column.text,
created_by: column.text,
completed_by: column.text,
completed: column.integer
},
{ indexes: { list: ['list_id'] } }
);
export const AppSchema = new Schema({
todos
});
```
```kotlin Kotlin theme={null}
import com.powersync.db.schema.Column
import com.powersync.db.schema.Schema
import com.powersync.db.schema.Table
import com.powersync.db.schema.Index
import com.powersync.db.schema.IndexedColumn
val AppSchema: Schema = Schema(
listOf(
Table(
name = "todos",
columns = listOf(
Column.text("list_id"),
Column.text("created_at"),
Column.text("completed_at"),
Column.text("description"),
Column.integer("completed"),
Column.text("created_by"),
Column.text("completed_by")
),
indexes = listOf(
Index("list", listOf(IndexedColumn.descending("list_id")))
)
)
)
)
```
```swift Swift theme={null}
import PowerSync
let todos = Table(
name: "todos",
columns: [
Column.text("list_id"),
Column.text("description"),
Column.integer("completed"),
Column.text("created_at"),
Column.text("completed_at"),
Column.text("created_by"),
Column.text("completed_by")
],
indexes: [
Index(
name: "list_id",
columns: [IndexedColumn.ascending("list_id")]
)
]
)
let AppSchema = Schema(todos)
```
```dart Dart/Flutter theme={null}
import 'package:powersync/powersync.dart';
const schema = Schema(([
Table('todos', [
Column.text('list_id'),
Column.text('created_at'),
Column.text('completed_at'),
Column.text('description'),
Column.integer('completed'),
Column.text('created_by'),
Column.text('completed_by'),
], indexes: [
Index('list', [IndexedColumn('list_id')])
])
]));
```
```csharp .NET theme={null}
using PowerSync.Common.DB.Schema;
class AppSchema
{
public static Table Todos = new Table(new Dictionary
{
{ "list_id", ColumnType.TEXT },
{ "created_at", ColumnType.TEXT },
{ "completed_at", ColumnType.TEXT },
{ "description", ColumnType.TEXT },
{ "created_by", ColumnType.TEXT },
{ "completed_by", ColumnType.TEXT },
{ "completed", ColumnType.INTEGER }
}, new TableOptions
{
Indexes = new Dictionary> { { "list", new List { "list_id" } } }
});
}
```
```rust Rust theme={null}
use powersync::schema::{Column, Schema, Table};
pub fn app_schema() -> Schema {
let mut schema = Schema::default();
let todos = Table::create(
"todos",
vec![
Column::text("list_id"),
Column::text("created_at"),
Column::text("completed_at"),
Column::text("description"),
Column::integer("completed"),
Column::text("created_by"),
Column::text("completed_by"),
],
|_| {},
);
schema.tables.push(todos);
schema
}
```
**Note**: The schema does not explicitly specify an `id` column, since PowerSync automatically creates an `id` column of type `text`. PowerSync [recommends](/sync/advanced/client-id) using UUIDs.
**Learn More**
The client-side schema uses three column types: `text`, `integer`, and `real`. These map directly to values from your Sync Rules and are automatically cast if needed. For details on how backend database types map to SQLite types, see [Types](/sync/types).
### Instantiate the PowerSync Database
Now that you have your client-side schema defined, instantiate the PowerSync database in your app. This creates the client-side SQLite database that will be kept in sync with your source database based on your Sync Rules configuration.
```typescript React Native (TS) theme={null}
import { PowerSyncDatabase } from '@powersync/react-native';
import { AppSchema } from './Schema';
export const db = new PowerSyncDatabase({
schema: AppSchema,
database: {
dbFilename: 'powersync.db'
}
});
```
```typescript Web (TS) theme={null}
import { PowerSyncDatabase } from '@powersync/web';
import { AppSchema } from './Schema';
export const db = new PowerSyncDatabase({
schema: AppSchema,
database: {
dbFilename: 'powersync.db'
}
});
```
```typescript Node.js (TS) theme={null}
import { PowerSyncDatabase } from '@powersync/node';
import { AppSchema } from './Schema';
export const db = new PowerSyncDatabase({
schema: AppSchema,
database: {
dbFilename: 'powersync.db'
}
});
```
```typescript Capacitor (TS) theme={null}
import { PowerSyncDatabase } from '@powersync/capacitor';
// Import general components from the Web SDK package
import { Schema } from '@powersync/web';
import { Connector } from './Connector';
import { AppSchema } from './AppSchema';
/**
* The Capacitor PowerSyncDatabase will automatically detect the platform
* and use the appropriate database drivers.
*/
export const db = new PowerSyncDatabase({
// The schema you defined in the previous step
schema: AppSchema,
database: {
// Filename for the SQLite database — it's important to only instantiate one instance per file.
dbFilename: 'powersync.db'
}
});
```
```kotlin Kotlin theme={null}
import com.powersync.DatabaseDriverFactory
import com.powersync.PowerSyncDatabase
// Android
val driverFactory = DatabaseDriverFactory(this)
// iOS & Desktop
// val driverFactory = DatabaseDriverFactory()
val database = PowerSyncDatabase({
factory: driverFactory,
schema: AppSchema,
dbFilename: "powersync.db"
})
```
```swift Swift theme={null}
import PowerSync
let db = PowerSyncDatabase(
schema: AppSchema,
dbFilename: "powersync.sqlite"
)
```
```dart Dart/Flutter theme={null}
import 'package:powersync/powersync.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart';
openDatabase() async {
final dir = await getApplicationSupportDirectory();
final path = join(dir.path, 'powersync-dart.db');
db = PowerSyncDatabase(schema: schema, path: path);
await db.initialize();
}
```
```csharp .NET - Common theme={null}
using PowerSync.Common.Client;
class Demo
{
static async Task Main()
{
var db = new PowerSyncDatabase(new PowerSyncDatabaseOptions
{
Database = new SQLOpenOptions { DbFilename = "tododemo.db" },
Schema = AppSchema.PowerSyncSchema,
});
await db.Init();
}
}
```
```csharp .NET - MAUI theme={null}
using PowerSync.Common.Client;
using PowerSync.Common.MDSQLite;
using PowerSync.Maui.SQLite;
class Demo
{
static async Task Main()
{
// Ensures the DB file is stored in a platform appropriate location
var dbPath = Path.Combine(FileSystem.AppDataDirectory, "maui-example.db");
var factory = new MAUISQLiteDBOpenFactory(new MDSQLiteOpenFactoryOptions()
{
DbFilename = dbPath
});
var Db = new PowerSyncDatabase(new PowerSyncDatabaseOptions()
{
Database = factory, // Supply a factory
Schema = AppSchema.PowerSyncSchema,
});
await db.Init();
}
}
```
```rust Rust theme={null}
// 1. Process setup: register PowerSync extension early (e.g. in main()).
// 2. Open a connection pool, create env, then database. Spawn async tasks
// before connecting (see Connect step). Requires powersync with tokio feature.
use powersync::{ConnectionPool, PowerSyncDatabase, error::PowerSyncError};
use powersync::env::PowerSyncEnvironment;
use std::sync::Arc;
use http_client::IsahcClient;
fn open_pool() -> Result {
ConnectionPool::open("powersync.db")
}
// This example shows the Tokio runtime. You must call
// `PowerSyncEnvironment::powersync_auto_extension()` before using the SDK and spawn async
// tasks with `db.async_tasks().spawn_with_tokio()` (or `spawn_with` for other runtimes)
// before connecting. See the Rust SDK reference for in-memory pools, smol, or custom runtimes.
#[tokio::main]
async fn main() {
PowerSyncEnvironment::powersync_auto_extension()
.expect("could not load PowerSync core extension");
let pool = open_pool().expect("open pool");
let client = Arc::new(IsahcClient::new());
let env = PowerSyncEnvironment::custom(
client.clone(),
pool,
Box::new(PowerSyncEnvironment::tokio_timer()),
);
let db = PowerSyncDatabase::new(env, app_schema());
db.async_tasks().spawn_with_tokio();
// Connect with a backend connector in the next step.
}
```
### Connect to PowerSync Service Instance
Connect your client-side PowerSync database to the PowerSync Service instance you created in [step 2](#2-set-up-powersync-service-instance) by defining a *backend connector* and calling `connect()`. The backend connector handles authentication and uploading mutations to your backend.
**Note**: This section assumes you want to use PowerSync to sync your backend source database with SQLite in your app. If you only want to use PowerSync to manage your local SQLite database without sync, instantiate the PowerSync database without calling `connect()` refer to our [Local-Only](/client-sdks/advanced/local-only-usage) guide.
You don't have to worry about the *backend connector* implementation details right now — you can leave the boilerplate as-is and come back to it later.
For development, you can use the development token you generated in the [Generate a Development Token](#optional-generate-a-development-token) step above. For production, you'll implement proper JWT authentication as we'll explain further below.
```typescript React Native (TS) theme={null}
import { AbstractPowerSyncDatabase, PowerSyncBackendConnector, PowerSyncCredentials } from '@powersync/react-native';
import { db } from './Database';
class Connector implements PowerSyncBackendConnector {
async fetchCredentials(): Promise {
// for development: use development token
return {
endpoint: 'https://your-instance.powersync.com',
token: 'your-development-token-here'
};
}
async uploadData(database: AbstractPowerSyncDatabase) {
const transaction = await database.getNextCrudTransaction();
if (!transaction) return;
for (const op of transaction.crud) {
const record = { ...op.opData, id: op.id };
// upload to your backend API
}
await transaction.complete();
}
}
// connect the database to PowerSync Service
const connector = new Connector();
await db.connect(connector);
```
```typescript Web & Capacitor (TS) theme={null}
import { AbstractPowerSyncDatabase, PowerSyncBackendConnector, PowerSyncCredentials } from '@powersync/web';
import { db } from './Database';
class Connector implements PowerSyncBackendConnector {
async fetchCredentials(): Promise {
// for development: use development token
return {
endpoint: 'https://your-instance.powersync.com',
token: 'your-development-token-here'
};
}
async uploadData(database: AbstractPowerSyncDatabase) {
const transaction = await database.getNextCrudTransaction();
if (!transaction) return;
for (const op of transaction.crud) {
const record = { ...op.opData, id: op.id };
// upload to your backend API
}
await transaction.complete();
}
}
// connect the database to PowerSync Service
const connector = new Connector();
await db.connect(connector);
```
```typescript Node.js (TS) theme={null}
import { PowerSyncBackendConnector } from '@powersync/node';
export class Connector implements PowerSyncBackendConnector {
async fetchCredentials() {
// for development: use development token
return {
endpoint: 'https://your-instance.powersync.com',
token: 'your-development-token-here'
};
}
async uploadData(database) {
// upload to your backend API
}
}
// connect the database to PowerSync Service
const connector = new Connector();
await db.connect(connector);
```
```kotlin Kotlin theme={null}
import com.powersync.PowerSyncCredentials
import com.powersync.PowerSyncDatabase
class MyConnector : PowerSyncBackendConnector {
override suspend fun fetchCredentials(): PowerSyncCredentials {
// for development: use development token
return PowerSyncCredentials(
endpoint = "https://your-instance.powersync.com",
token = "your-development-token-here"
)
}
override suspend fun uploadData(database: PowerSyncDatabase) {
val transaction = database.getNextCrudTransaction() ?: return
for (op in transaction.crud) {
val record = op.opData + ("id" to op.id)
// upload to your backend API
}
transaction.complete()
}
}
// connect the database to PowerSync Service
database.connect(MyConnector())
```
```swift Swift theme={null}
import PowerSync
class Connector: PowerSyncBackendConnector {
func fetchCredentials() async throws -> PowerSyncCredentials {
// for development: use development token
return PowerSyncCredentials(
endpoint: "https://your-instance.powersync.com",
token: "your-development-token-here"
)
}
func uploadData(database: PowerSyncDatabase) async throws {
guard let transaction = try await database.getNextCrudTransaction() else {
return
}
for op in transaction.crud {
var record = op.opData
record["id"] = op.id
// upload to your backend API
}
try await transaction.complete()
}
}
// connect the database to PowerSync Service
let connector = Connector()
await db.connect(connector: connector)
```
```dart Dart/Flutter theme={null}
import 'package:powersync/powersync.dart';
class Connector extends PowerSyncBackendConnector {
@override
Future fetchCredentials() async {
return PowerSyncCredentials(
endpoint: 'https://your-instance.powersync.com',
token: 'your-development-token-here'
);
}
@override
Future uploadData(PowerSyncDatabase database) async {
final transaction = await database.getNextCrudTransaction();
if (transaction == null) return;
for (final op in transaction.crud) {
final record = {...op.opData, 'id': op.id};
// upload to your backend API
}
await transaction.complete();
}
}
// connect the database to PowerSync Service
final connector = Connector();
await db.connect(connector);
```
```csharp .NET theme={null}
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using PowerSync.Common.Client;
using PowerSync.Common.Client.Connection;
using PowerSync.Common.DB.Crud;
public class MyConnector : IPowerSyncBackendConnector
{
public MyConnector()
{
}
public async Task FetchCredentials()
{
var powerSyncUrl = "https://your-instance.powersync.com";
var authToken = "your-development-token-here";
// Return credentials with PowerSync endpoint and JWT token
return new PowerSyncCredentials(powerSyncUrl, authToken);
}
public async Task UploadData(IPowerSyncDatabase database)
{
// upload to your backend API
}
}
// connect the database to PowerSync Service
await db.Connect(new MyConnector());
```
```rust Rust theme={null}
use async_trait::async_trait;
use powersync::{BackendConnector, PowerSyncCredentials, PowerSyncDatabase, SyncOptions};
use powersync::error::PowerSyncError;
use std::sync::Arc;
struct MyBackendConnector {
client: Arc,
db: PowerSyncDatabase,
}
#[async_trait]
impl BackendConnector for MyBackendConnector {
async fn fetch_credentials(&self) -> Result {
// for development: use development token
Ok(PowerSyncCredentials {
endpoint: "https://your-instance.powersync.com".to_string(),
token: "your-development-token-here".to_string(),
})
}
async fn upload_data(&self) -> Result<(), PowerSyncError> {
let mut local_writes = self.db.crud_transactions();
while let Some(tx) = local_writes.try_next().await? {
// upload to your backend API
tx.complete().await?;
}
Ok(())
}
}
// connect the database to PowerSync Service
db.connect(SyncOptions::new(MyBackendConnector {
client,
db: db.clone(),
}))
.await;
```
Once connected, you can read from and write to the client-side SQLite database. Changes from your source database will be automatically synced down into the SQLite database. For client-side mutations to be uploaded back to your source database, you need to complete the backend integration as we'll explain below.
### Read Data
Read data using SQL queries. The data comes from your client-side SQLite database:
```typescript React Native, Web, Node.js & Capacitor (TS) theme={null}
// Get all todos
const todos = await db.getAll('SELECT * FROM todos');
// Get a single todo
const todo = await db.get('SELECT * FROM todos WHERE id = ?', [todoId]);
// Watch for changes (reactive query)
const stream = db.watch('SELECT * FROM todos WHERE list_id = ?', [listId]);
for await (const todos of stream) {
// Update UI when data changes
console.log(todos);
}
// Note: The above example requires async iterator support in React Native.
// If you encounter issues, use one of these callback-based APIs instead:
// Option 1: Using onResult callback
// const abortController = new AbortController();
// db.watch(
// 'SELECT * FROM todos WHERE list_id = ?',
// [listId],
// {
// onResult: (todos) => {
// // Update UI when data changes
// console.log(todos);
// }
// },
// { signal: abortController.signal }
// );
// Option 2: Using the query builder API
// const query = db
// .query({
// sql: 'SELECT * FROM todos WHERE list_id = ?',
// parameters: [listId]
// })
// .watch();
// query.registerListener({
// onData: (todos) => {
// // Update UI when data changes
// console.log(todos);
// }
// });
```
```kotlin Kotlin theme={null}
// Get all todos
val todos = database.getAll("SELECT * FROM todos") { cursor ->
Todo.fromCursor(cursor)
}
// Get a single todo
val todo = database.get("SELECT * FROM todos WHERE id = ?", listOf(todoId)) { cursor ->
Todo.fromCursor(cursor)
}
// Watch for changes
database.watch("SELECT * FROM todos WHERE list_id = ?", listOf(listId))
.collect { todos ->
// Update UI when data changes
}
```
```swift Swift theme={null}
// Get all todos
let todos = try await db.getAll(
sql: "SELECT * FROM todos",
mapper: { cursor in
TodoContent(
description: try cursor.getString(name: "description")!,
completed: try cursor.getBooleanOptional(name: "completed")
)
}
)
// Watch for changes
for try await todos in db.watch(
sql: "SELECT * FROM todos WHERE list_id = ?",
parameters: [listId]
) {
// Update UI when data changes
}
```
```dart Dart/Flutter theme={null}
// Get all todos
final todos = await db.getAll('SELECT * FROM todos');
// Get a single todo
final todo = await db.get('SELECT * FROM todos WHERE id = ?', [todoId]);
// Watch for changes
db.watch('SELECT * FROM todos WHERE list_id = ?', [listId])
.listen((todos) {
// Update UI when data changes
});
```
```csharp .NET theme={null}
// Define a result type with properties matching schema columns (some columns omitted for brevity)
// public class ListResult { public string id; public string name; public string owner_id; public string created_at; ... }
// Use db.Get() to fetch a single row:
var list = await db.Get("SELECT * FROM lists WHERE id = ?", [listId]);
// Use db.GetAll() to fetch all rows:
var lists = await db.GetAll("SELECT * FROM lists");
// Watch for changes to query results
var query = await db.Watch("SELECT * FROM lists", null, new WatchHandler
{
OnResult = (results) => Console.WriteLine($"Lists updated: {results.Length} items"),
OnError = (error) => Console.WriteLine($"Error: {error.Message}")
});
// Call query.Dispose() to stop watching for updates
query.Dispose();
```
```rust Rust theme={null}
use rusqlite::params;
use futures::StreamExt; // for try_next() on the watch stream
// Get all todos
async fn get_all_todos(db: &PowerSyncDatabase) -> Result<(), PowerSyncError> {
let reader = db.reader().await?;
let mut stmt = reader.prepare("SELECT * FROM todos")?;
let mut rows = stmt.query(params![])?;
while let Some(row) = rows.next()? {
let id: String = row.get("id")?;
let description: String = row.get("description")?;
// use row data
}
Ok(())
}
// Get a single todo
async fn find_todo(db: &PowerSyncDatabase, todo_id: &str) -> Result<(), PowerSyncError> {
let reader = db.reader().await?;
let mut stmt = reader.prepare("SELECT * FROM todos WHERE id = ?")?;
let mut rows = stmt.query(params![todo_id])?;
while let Some(row) = rows.next()? {
let id: String = row.get("id")?;
let description: String = row.get("description")?;
println!("Found todo: {id}, {description}");
}
Ok(())
}
// Watch for changes
async fn watch_todos(db: &PowerSyncDatabase, list_id: &str) -> Result<(), PowerSyncError> {
let stream = db.watch_statement(
"SELECT * FROM todos WHERE list_id = ?".to_string(),
params![list_id],
|stmt, params| {
let mut rows = stmt.query(params)?;
let mut mapped = vec![];
while let Some(row) = rows.next()? {
mapped.push(() /* TODO: Read row into struct */);
}
Ok(mapped)
},
);
let mut stream = std::pin::pin!(stream);
while let Some(_event) = stream.try_next().await? {
// Update UI when data changes
}
Ok(())
}
```
**Learn More**
* [Reading Data](/client-sdks/reading-data) - Details on querying synced data
* [ORMs Overview](/client-sdks/orms/overview) - Using type-safe ORMs with PowerSync
* [Live Queries / Watch Queries](/client-sdks/watch-queries) - Building reactive UIs with automatic updates
### Write Data
Write data using SQL `INSERT`, `UPDATE`, or `DELETE` statements. PowerSync automatically queues these mutations and uploads them to your backend via the `uploadData()` function, once you've fully implemented your *backend connector* (as we'll talk about below).
```typescript React Native (TS), Web & Node.js theme={null}
// Insert a new todo
await db.execute(
'INSERT INTO todos (id, created_at, list_id, description) VALUES (uuid(), date(), ?, ?)',
[listId, 'Buy groceries']
);
// Update a todo
await db.execute(
'UPDATE todos SET completed = 1, completed_at = date() WHERE id = ?',
[todoId]
);
// Delete a todo
await db.execute('DELETE FROM todos WHERE id = ?', [todoId]);
```
```kotlin Kotlin theme={null}
// Insert a new todo
database.writeTransaction { ctx ->
ctx.execute(
sql = "INSERT INTO todos (id, created_at, list_id, description) VALUES (uuid(), date(), ?, ?)",
parameters = listOf(listId, "Buy groceries")
)
}
// Update a todo
database.execute(
sql = "UPDATE todos SET completed = 1, completed_at = date() WHERE id = ?",
parameters = listOf(todoId)
)
// Delete a todo
database.execute(
sql = "DELETE FROM todos WHERE id = ?",
parameters = listOf(todoId)
)
```
```swift Swift theme={null}
// Insert a new todo
try await db.execute(
sql: "INSERT INTO todos (id, created_at, list_id, description) VALUES (uuid(), date(), ?, ?)",
parameters: [listId, "Buy groceries"]
)
// Update a todo
try await db.execute(
sql: "UPDATE todos SET completed = 1, completed_at = date() WHERE id = ?",
parameters: [todoId]
)
// Delete a todo
try await db.execute(
sql: "DELETE FROM todos WHERE id = ?",
parameters: [todoId]
)
```
```dart Dart/Flutter theme={null}
// Insert a new todo
await db.execute(
'INSERT INTO todos (id, created_at, list_id, description) VALUES (uuid(), date(), ?, ?)',
[listId, 'Buy groceries']
);
// Update a todo
await db.execute(
'UPDATE todos SET completed = 1, completed_at = date() WHERE id = ?',
[todoId]
);
// Delete a todo
await db.execute('DELETE FROM todos WHERE id = ?', [todoId]);
```
```csharp .NET theme={null}
// Insert a new todo
await db.Execute(
"INSERT INTO todos (id, created_at, list_id, description) VALUES (uuid(), datetime(), ?, ?)",
[listId, "Buy groceries"]
);
// Update a todo
await db.Execute(
"UPDATE todos SET completed = 1, completed_at = datetime() WHERE id = ?",
[todoId]
);
// Delete a todo
await db.Execute("DELETE FROM todos WHERE id = ?", [todoId]);
```
```rust Rust theme={null}
use rusqlite::params;
// Insert a new todo
async fn insert_todo(
db: &PowerSyncDatabase,
list_id: &str,
description: &str,
) -> Result<(), PowerSyncError> {
let writer = db.writer().await?;
writer.execute(
"INSERT INTO todos (id, created_at, list_id, description) VALUES (uuid(), date(), ?, ?)",
params![list_id, description],
)?;
Ok(())
}
// Update a todo
async fn complete_todo(db: &PowerSyncDatabase, todo_id: &str) -> Result<(), PowerSyncError> {
let writer = db.writer().await?;
writer.execute(
"UPDATE todos SET completed = 1, completed_at = date() WHERE id = ?",
params![todo_id],
)?;
Ok(())
}
// Delete a todo
async fn delete_todo(db: &PowerSyncDatabase, todo_id: &str) -> Result<(), PowerSyncError> {
let writer = db.writer().await?;
writer.execute("DELETE FROM todos WHERE id = ?", params![todo_id])?;
Ok(())
}
```
**Best practice**: Use UUIDs when inserting new rows on the client side. UUIDs can be generated offline/locally, allowing for unique identification of records created in the client database before they are synced to the server. See [Client ID](/sync/advanced/client-id) for more details.
**Learn More**
For more details, see the [Writing Data](/client-sdks/writing-data) page.
# Next Steps
For production deployments, you'll need to:
1. **[Implement Authentication](/configuration/auth/overview)**: Replace development tokens with proper JWT-based authentication. PowerSync supports various authentication providers including Supabase, Firebase Auth, Auth0, Clerk, and custom JWT implementations.
2. **Configure & Integrate Your Backend Application**: Set up your backend to handle mutations uploaded from clients.
* [Server-Side Setup](/configuration/app-backend/setup)
* [Client-Side Integration](/configuration/app-backend/client-side-integration)
### Additional Resources
* Learn more about [Sync Rules](/sync/rules/overview) for advanced data filtering
* Explore [Live Queries / Watch Queries](/client-sdks/watch-queries) for reactive UI updates
* Check out [Example Projects](/intro/examples) for complete implementations
* Review the [Client SDK References](/client-sdks/overview) for client-side platform-specific details
# Questions?
Try "Ask AI" on this site which is trained on all our documentation, repositories and Discord discussions. Also join us on [our community Discord server](https://discord.gg/powersync) where you can browse topics from the PowerSync community and chat with our team.