Use this file to discover all available pages before exploring further.
The PowerSync integration is fully built and production-ready. The cloud sync component will become publicly available when the Despia V4 editor launches. To enable it already contact our support via the Despia live chat and share your PowerSync URL + Bundle ID.
Returns true when the native PowerSync runtime is present, false in a standard browser. Use this to guard all database calls when your web app also runs outside Despia.
import { active } from '@despia/powersync'if (!active()) { return // standard browser, no native runtime}
active() only confirms runtime presence. It does not mean SQLite is initialized or sync is connected.
Describes the tables and columns native PowerSync should map. Column types must be exactly "text", "integer", or "real". Indexes are optional and map an index name to an array of column names that exist in the same table.
schemaVersion
Yes
Positive integer matching the latest migration version for this schema. Native promotes the pending schema to active only after migrations reach this version.
databaseName
No
Optional database name. Native uses a default if omitted.
db.init() validates the schema object before calling native. If validation fails it throws a PowerSyncError with code: "invalid_schema" and a details array listing every path that failed.
Apply pending SQL statements to the SQLite file. Native tracks the installed version and only executes statements for versions higher than the current one. Pass all pending statements for a version in a single call so native can commit or roll back the full upgrade as one transaction.
await db.migrate(1, [ `CREATE TABLE IF NOT EXISTS users ( id TEXT PRIMARY KEY, email TEXT NOT NULL, createdAt TEXT NOT NULL )`, 'CREATE INDEX IF NOT EXISTS users_by_email ON users(email)',])await db.migrate(2, [ 'ALTER TABLE users ADD COLUMN displayName TEXT',])
Parameter
Required
Description
version
Yes
Target migration version. Should match SCHEMA_VERSION. Native promotes the pending schema to active when this version matches schemaVersion.
statements
Yes
Array of SQL strings or BatchStatement objects to run for this version. Must be non-empty.
When your schema is at version N and the device needs migrations 1 through N, pass all pending statements in one db.migrate(N, statements) call rather than calling migrate() once per version.
Fetches all matching rows from local SQLite. Instant, no network.
type User = { id: string; email: string }const users = await db.query<User>( 'SELECT id, email FROM users WHERE active = ? AND role = ?', [1, 'admin'])
Starts sync for the signed-in user. Pass a short-lived JWT minted by your backend. Native reads the static PowerSync app ID and instance URL from native config.
Fires the callback whenever sync state changes. Use this for sync indicators, offline banners, and last-synced timestamps. For row data, use db.watch() instead.
If the handler resolves, the SDK tells native upload_complete and the native queue is finalized. If the handler throws, the SDK reports the error back to native and PowerSync retries later. Calling events.upload() again replaces the previous handler, so register one handler per session.
Creates or updates a native full-text index on a table. Resolves when the index is fully built, including asynchronous builds on large tables. Native validates that the source table and columns exist.
db.powersync.connect({ token }) gives the native sync engine a token for the current signed-in user. It does not log the user into your app and does not create the token. Three separate pieces are involved: your app auth decides who the user is, PowerSync Client Auth verifies the token native sends to PowerSync, and PowerSync sync rules decide which rows that verified user can sync.The token identifies the user. If a row syncs into local SQLite, your app can query it. Private rows must be blocked by sync rules before they reach the device.
Your backend signs the JWT with the key or secret configured in PowerSync Client Auth. PowerSync verifies the signature, kid, audience, expiry, and subject.
The application user ID. PowerSync sync rules use this as the user identity.
aud
Must match the PowerSync instance URL or configured audience.
iat / exp
Token issue and expiry times. Keep expiry short.
kid
Header key id. Must match the key configured in PowerSync Client Auth.
custom claims
Optional data such as team, role, or project claims if your sync rules need them.
For production, asymmetric JWT signing with JWKS is preferred so PowerSync can verify tokens with a public key while your backend keeps the private signing key. HS256 works for development if it exactly matches your PowerSync Client Auth configuration.
If your app already uses Supabase Auth or Firebase Auth, you may not need a custom token endpoint. PowerSync can verify those provider JWTs directly when Client Auth, audience, and JWKS settings match your provider.
Schema describes the expected shape. Migration SQL changes the actual SQLite file. You always need both.Keep schema and migrations together in one module:
import type { PowerSyncSchema } from '@despia/powersync'export const DATABASE_NAME = 'mydb'export const SCHEMA_VERSION = 2export const CURRENT_SCHEMA: PowerSyncSchema = { users: { columns: { id: 'text', email: 'text', createdAt: 'text' }, indexes: { users_by_email: ['email'] }, }, posts: { columns: { id: 'text', userId: 'text', title: 'text', body: 'text', createdAt: 'text' }, indexes: { posts_by_user: ['userId'] }, },}export const MIGRATIONS = [ { version: 1, statements: [ 'CREATE TABLE IF NOT EXISTS users (id TEXT PRIMARY KEY, email TEXT NOT NULL, createdAt TEXT NOT NULL)', 'CREATE INDEX IF NOT EXISTS users_by_email ON users(email)', ], }, { version: 2, statements: [ 'CREATE TABLE IF NOT EXISTS posts (id TEXT PRIMARY KEY, userId TEXT NOT NULL, title TEXT NOT NULL, body TEXT, createdAt TEXT NOT NULL)', 'CREATE INDEX IF NOT EXISTS posts_by_user ON posts(userId)', ], },]
When the schema changes: update CURRENT_SCHEMA, increase SCHEMA_VERSION, add a migration with the new version, run all pending statements with db.migrate(SCHEMA_VERSION, pendingStatements), and only then run queries or sync that depend on the new shape.
await db.migrate(4, [ 'CREATE TABLE IF NOT EXISTS comments (id TEXT PRIMARY KEY, postId TEXT NOT NULL, body TEXT NOT NULL)', 'CREATE INDEX IF NOT EXISTS comments_by_post ON comments(postId)',])
Use copy-and-swap. SQLite does not support ALTER TABLE ... RENAME COLUMN on older versions.
await db.migrate(5, [ `CREATE TABLE users_new ( id TEXT PRIMARY KEY, email TEXT NOT NULL, displayName TEXT, createdAt TEXT NOT NULL )`, 'INSERT INTO users_new (id, email, displayName, createdAt) SELECT id, email, NULL, createdAt FROM users', 'DROP TABLE users', 'ALTER TABLE users_new RENAME TO users',])
import { active, db, Database, onEvent, type BatchResult, type BatchStatement, type ExecuteResult, type PowerSyncConfig, type PowerSyncColumnType, type PowerSyncCrudEntry, type PowerSyncCrudOperation, type PowerSyncError, type PowerSyncErrorDetail, type PowerSyncErrorDetails, type PowerSyncInitOptions, type PowerSyncSchema, type PowerSyncSchemaState, type PowerSyncSearchDropIndexOptions, type PowerSyncSearchIndex, type PowerSyncSearchIndexBuiltEvent, type PowerSyncSearchIndexFailedEvent, type PowerSyncSearchIndexOptions, type PowerSyncSearchIndexProgressEvent, type PowerSyncSearchIndexResult, type PowerSyncSearchIndexState, type PowerSyncSearchIndexStatus, type PowerSyncSearchMode, type PowerSyncSearchQueryOptions, type PowerSyncSearchRebuildIndexOptions, type PowerSyncSearchRow, type PowerSyncTableSchema, type PowerSyncUploadHandler, type PowerSyncUploadPayload, type SyncStatus,} from '@despia/powersync'
Returns true when the native PowerSync runtime is present, false in a standard browser. Use this to guard all database calls when your web app also runs outside Despia.
import { active } from '@despia/powersync'if (!active()) { return // standard browser, no native runtime}
active() only confirms runtime presence. It does not mean SQLite is initialized or sync is connected.
Describes the tables and columns native PowerSync should map. Column types must be exactly "text", "integer", or "real". Indexes are optional and map an index name to an array of column names that exist in the same table.
schemaVersion
Yes
Positive integer matching the latest migration version for this schema. Native promotes the pending schema to active only after migrations reach this version.
databaseName
No
Optional database name. Native uses a default if omitted.
db.init() validates the schema object before calling native. If validation fails it throws a PowerSyncError with code: "invalid_schema" and a details array listing every path that failed.
Apply pending SQL statements to the SQLite file. Native tracks the installed version and only executes statements for versions higher than the current one. Pass all pending statements for a version in a single call so native can commit or roll back the full upgrade as one transaction.
await db.migrate(1, [ `CREATE TABLE IF NOT EXISTS users ( id TEXT PRIMARY KEY, email TEXT NOT NULL, createdAt TEXT NOT NULL )`, 'CREATE INDEX IF NOT EXISTS users_by_email ON users(email)',])await db.migrate(2, [ 'ALTER TABLE users ADD COLUMN displayName TEXT',])
Parameter
Required
Description
version
Yes
Target migration version. Should match SCHEMA_VERSION. Native promotes the pending schema to active when this version matches schemaVersion.
statements
Yes
Array of SQL strings or BatchStatement objects to run for this version. Must be non-empty.
When your schema is at version N and the device needs migrations 1 through N, pass all pending statements in one db.migrate(N, statements) call rather than calling migrate() once per version.
Fetches all matching rows from local SQLite. Instant, no network.
type User = { id: string; email: string }const users = await db.query<User>( 'SELECT id, email FROM users WHERE active = ? AND role = ?', [1, 'admin'])
Starts sync for the signed-in user. Pass a short-lived JWT minted by your backend. Native reads the static PowerSync app ID and instance URL from native config.
Fires the callback whenever sync state changes. Use this for sync indicators, offline banners, and last-synced timestamps. For row data, use db.watch() instead.
If the handler resolves, the SDK tells native upload_complete and the native queue is finalized. If the handler throws, the SDK reports the error back to native and PowerSync retries later. Calling events.upload() again replaces the previous handler, so register one handler per session.
Creates or updates a native full-text index on a table. Resolves when the index is fully built, including asynchronous builds on large tables. Native validates that the source table and columns exist.
db.powersync.connect({ token }) gives the native sync engine a token for the current signed-in user. It does not log the user into your app and does not create the token. Three separate pieces are involved: your app auth decides who the user is, PowerSync Client Auth verifies the token native sends to PowerSync, and PowerSync sync rules decide which rows that verified user can sync.The token identifies the user. If a row syncs into local SQLite, your app can query it. Private rows must be blocked by sync rules before they reach the device.
Your backend signs the JWT with the key or secret configured in PowerSync Client Auth. PowerSync verifies the signature, kid, audience, expiry, and subject.
The application user ID. PowerSync sync rules use this as the user identity.
aud
Must match the PowerSync instance URL or configured audience.
iat / exp
Token issue and expiry times. Keep expiry short.
kid
Header key id. Must match the key configured in PowerSync Client Auth.
custom claims
Optional data such as team, role, or project claims if your sync rules need them.
For production, asymmetric JWT signing with JWKS is preferred so PowerSync can verify tokens with a public key while your backend keeps the private signing key. HS256 works for development if it exactly matches your PowerSync Client Auth configuration.
If your app already uses Supabase Auth or Firebase Auth, you may not need a custom token endpoint. PowerSync can verify those provider JWTs directly when Client Auth, audience, and JWKS settings match your provider.
Schema describes the expected shape. Migration SQL changes the actual SQLite file. You always need both.Keep schema and migrations together in one module:
import type { PowerSyncSchema } from '@despia/powersync'export const DATABASE_NAME = 'mydb'export const SCHEMA_VERSION = 2export const CURRENT_SCHEMA: PowerSyncSchema = { users: { columns: { id: 'text', email: 'text', createdAt: 'text' }, indexes: { users_by_email: ['email'] }, }, posts: { columns: { id: 'text', userId: 'text', title: 'text', body: 'text', createdAt: 'text' }, indexes: { posts_by_user: ['userId'] }, },}export const MIGRATIONS = [ { version: 1, statements: [ 'CREATE TABLE IF NOT EXISTS users (id TEXT PRIMARY KEY, email TEXT NOT NULL, createdAt TEXT NOT NULL)', 'CREATE INDEX IF NOT EXISTS users_by_email ON users(email)', ], }, { version: 2, statements: [ 'CREATE TABLE IF NOT EXISTS posts (id TEXT PRIMARY KEY, userId TEXT NOT NULL, title TEXT NOT NULL, body TEXT, createdAt TEXT NOT NULL)', 'CREATE INDEX IF NOT EXISTS posts_by_user ON posts(userId)', ], },]
When the schema changes: update CURRENT_SCHEMA, increase SCHEMA_VERSION, add a migration with the new version, run all pending statements with db.migrate(SCHEMA_VERSION, pendingStatements), and only then run queries or sync that depend on the new shape.
await db.migrate(4, [ 'CREATE TABLE IF NOT EXISTS comments (id TEXT PRIMARY KEY, postId TEXT NOT NULL, body TEXT NOT NULL)', 'CREATE INDEX IF NOT EXISTS comments_by_post ON comments(postId)',])
Use copy-and-swap. SQLite does not support ALTER TABLE ... RENAME COLUMN on older versions.
await db.migrate(5, [ `CREATE TABLE users_new ( id TEXT PRIMARY KEY, email TEXT NOT NULL, displayName TEXT, createdAt TEXT NOT NULL )`, 'INSERT INTO users_new (id, email, displayName, createdAt) SELECT id, email, NULL, createdAt FROM users', 'DROP TABLE users', 'ALTER TABLE users_new RENAME TO users',])
import { active, db, Database, onEvent, type BatchResult, type BatchStatement, type ExecuteResult, type PowerSyncConfig, type PowerSyncColumnType, type PowerSyncCrudEntry, type PowerSyncCrudOperation, type PowerSyncError, type PowerSyncErrorDetail, type PowerSyncErrorDetails, type PowerSyncInitOptions, type PowerSyncSchema, type PowerSyncSchemaState, type PowerSyncSearchDropIndexOptions, type PowerSyncSearchIndex, type PowerSyncSearchIndexBuiltEvent, type PowerSyncSearchIndexFailedEvent, type PowerSyncSearchIndexOptions, type PowerSyncSearchIndexProgressEvent, type PowerSyncSearchIndexResult, type PowerSyncSearchIndexState, type PowerSyncSearchIndexStatus, type PowerSyncSearchMode, type PowerSyncSearchQueryOptions, type PowerSyncSearchRebuildIndexOptions, type PowerSyncSearchRow, type PowerSyncTableSchema, type PowerSyncUploadHandler, type PowerSyncUploadPayload, type SyncStatus,} from '@despia/powersync'