
Enables type-safe SQLite database access with auto-generated code from SQL files, comment-based annotations for code control, and no need for IDE plugins, focusing on SQLite-specific optimizations.
If SQLiteNow saves you time, please consider starring ⭐ the repository - it helps more KMP, Flutter/Dart, and Swift developers find it.
SQLiteNow is SQL-first tooling for type-safe SQLite access in Kotlin Multiplatform and Flutter/Dart apps, plus native Swift apps through a local generated-package workflow. Write schema, migration, and query files in SQL, then generate platform-native code with typed parameters, typed results, migrations, transactions, and reactive invalidation.
Full documentation is available at https://mobiletoly.github.io/sqlitenow-kmp/.
SQLiteNow keeps SQL as the source of truth while still giving application code a typed API. It is focused exclusively on SQLite instead of abstracting over multiple database engines, which leaves room for SQLite-specific behavior, annotations, migrations, and sync-aware generated code.
The main goals are:
.sql files for schema, migrations, and
queries.-- @@{ queryResult=... }, dynamicField, and mapTo to shape generated
models.SQLiteNow can shape generated result models directly from SQL annotations:
-- @@{ queryResult=PersonWithAddresses }
SELECT p.id,
p.first_name,
p.last_name,
p.email,
p.created_at,
a.address_type,
a.postal_code,
a.country,
a.street,
a.city,
a.state
/* @@{ dynamicField=addresses,
mappingType=collection,
propertyType=List<Address>,
sourceTable=a,
collectionKey=address_id } */
FROM Person p
LEFT JOIN PersonAddress a ON p.id = a.person_id
ORDER BY p.id, a.address_type
LIMIT :limit OFFSET :offsetThis generates a PersonWithAddresses result model with
addresses: List<Address> plus typed query parameters for limit and offset.
Adapters can convert between SQLite values and domain types, and mapTo can
shape data further when the generated model should match application-layer
types.
Full examples are available in /sample-kmp for KMP,
/dart/examples for Dart/Flutter, and
/swift/samples/core for native Swift.
| Platform | Use this when | Status | Start here |
|---|---|---|---|
| Kotlin Multiplatform | You are building shared Kotlin apps for Android, iOS, desktop, JS, or Wasm. | Released Gradle plugin and KMP runtime libraries | KMP guide |
| Flutter/Dart | You are building Flutter apps or pure Dart packages. | Released Dart runtime and CLI packages | Flutter/Dart guide |
| Native Swift local package | You want Swift/Xcode code to consume a generated local SwiftPM package. | Local generated package workflow; release artifacts still in progress | Swift guide |
Supported target details:
macosArm64, linuxX64, linuxArm64,
JVM desktop/server, JavaScript browser, and Kotlin/Wasm browser.package:sqlite3,
plus pure Dart packages through the Dart runtime.Native Swift support currently uses local generated Swift packages on Apple
platforms. Core Swift apps can keep SQL in a Swift/Xcode repository, run the
sqlitenow-generate SwiftPM command plugin, and import the generated package
product. The generator still requires Java 17 or newer, and published runtime
binary artifacts, a Swift-native generator/runtime, and a public SWIFT
compiler backend are not released yet. Native Swift runtime artifacts are
arm64-only: macosArm64, iosArm64, and iosSimulatorArm64.
Each platform has a complete setup guide. The short version is the same across all runtimes: keep SQL in your app repository, configure one or more databases, run the generator, and use the generated API from application code.
Full setup: KMP getting started
KMP apps use the dev.goquick.sqlitenow Gradle plugin and the
dev.goquick.sqlitenow:core runtime dependency. Configure one or more
databases in Gradle:
sqliteNow {
databases {
create("SampleDatabase") {
packageName.set("com.example.app.db")
}
}
}Place SQL files under the matching database directory:
src/commonMain/sql/SampleDatabase/
schema/
queries/
init/
migration/
Generate the database API with the task created from the database name:
./gradlew :composeApp:generateSampleDatabaseReplace :composeApp with your KMP module path and SampleDatabase with your
database name. Generated Kotlin is written under build/generated/sqlitenow/code
and is wired into commonMain by the Gradle plugin. Treat that directory as
generated output: edit SQL/configuration and rerun the task instead of
hand-editing the generated files.
For a complete app walkthrough, follow the Mood Tracker tutorial series and browse the sample project.
Full setup: Flutter/Dart getting started
Flutter and Dart apps use sqlitenow_runtime plus sqlitenow_cli:
dependencies:
sqlitenow_runtime: ^X.Y.Z
dev_dependencies:
sqlitenow_cli: ^X.Y.ZConfigure generation in sqlitenow.yaml:
databases:
AppDatabase:
input: lib/db/sql/AppDatabase
output: lib/db/generated
package: app.db
runtime: dartGenerate Dart code:
flutter pub run sqlitenow_cli generateFor pure Dart packages, use:
dart run sqlitenow_cli generateGenerated Dart is written to the configured output directory, such as
lib/db/generated/app_database.dart. Edit SQL/configuration and regenerate
instead of hand-editing generated Dart.
Full setup: Swift getting started
Swift apps add the released SQLiteNow SwiftPM package to the Swift package that
owns the SQL files, then configure generation with SQLiteNow.json:
{
"schemaVersion": 1,
"databases": [
{
"databaseName": "AppDatabase",
"swiftPackageName": "AppDatabaseSQLiteNow",
"swiftTargetName": "AppDatabaseSQLiteNow",
"runtime": "core"
}
]
}By default, SQL is read from:
SQLiteNow/databases/AppDatabase/
Generate the local Swift package from the Swift package root:
swift package plugin --allow-writing-to-package-directory sqlitenow-generateBy default, the generated package is written to:
SQLiteNowGenerated/AppDatabaseSQLiteNow
Add that generated directory as a local SwiftPM dependency, link the generated product to the app target, and import it from Swift:
import AppDatabaseSQLiteNowTreat SQLiteNowGenerated/ as generated output. Change SQL or
SQLiteNow.json, then rerun sqlitenow-generate.
SQLiteNow includes Oversqlite, an optional synchronization system for multi-device applications with conflict resolution and offline-first behavior. Sync setup requires three explicit pieces on each client:
The sync system automatically handles:
If you have a table to sync, annotate it with enableSync=true:
-- Enable sync for this table
-- @@{ enableSync=true }
CREATE TABLE person (
id TEXT PRIMARY KEY NOT NULL,
first_name TEXT NOT NULL,
last_name TEXT NOT NULL,
email TEXT UNIQUE,
created_at INTEGER NOT NULL DEFAULT (unixepoch())
);Sync-enabled tables must use exactly one local PRIMARY KEY column of type TEXT or BLOB.
INTEGER/BIGINT sync keys are rejected, and local sync-enabled tables must not model the
reserved server scope column _sync_scope_id.
Add the KMP runtime artifacts and enable Oversqlite in the Gradle DSL:
commonMain.dependencies {
implementation("dev.goquick.sqlitenow:core:<version>")
implementation("dev.goquick.sqlitenow:oversqlite:<version>")
}
sqliteNow {
databases {
create("AppDatabase") {
packageName = "com.example.app.db"
oversqlite = true
}
}
}Then use the generated sync client in your application:
// Create authenticated HTTP client with JWT token refresh and base URL
val httpClient = HttpClient {
install(Auth) {
bearer {
loadTokens { /* load saved token */ }
refreshTokens { /* refresh when expired */ }
}
}
defaultRequest {
url("https://api.myapp.com")
}
}
// Create sync client
val syncClient = db.newOversqliteClient(
schema = "myapp",
httpClient = httpClient,
resolver = ServerWinsResolver
)
// Open local runtime and attach the authenticated account.
syncClient.open().getOrThrow()
syncClient.attach(userId = "user123").getOrThrow()
// Perform full sync (upload local changes, download remote changes)
syncClient.sync().getOrThrow()
// Optional: start default-off automatic downloads.
// Bundle-change watch is only a wake-up hint; pullToStable() remains authoritative.
val automaticDownloads = coroutineScope.launch {
syncClient.runAutomaticDownloads(
db.buildOversqliteAutomaticDownloadConfig(
bundleChangeWatchMode = BundleChangeWatchMode.AUTO,
),
)
}
automaticDownloads.cancelAndJoin()KMP sync example: /samplesync-kmp.
Add the Dart runtime packages and enable Oversqlite in sqlitenow.yaml.
Replace X.Y.Z with the latest SQLiteNow release version.
dependencies:
sqlitenow_runtime: ^X.Y.Z
sqlitenow_oversqlite: ^X.Y.Z
dev_dependencies:
sqlitenow_cli: ^X.Y.Zdatabases:
AppDatabase:
input: lib/db/sql/AppDatabase
output: lib/db/generated
package: app.db
runtime: dart
oversqlite: trueThen use the generated sync client in your application:
final httpClient = IoOversqliteHttpClient(
baseUri: Uri.parse('https://api.myapp.com'),
defaultHeaders: {
HttpHeaders.authorizationHeader: 'Bearer $token',
},
);
final syncClient = db.newOversqliteClient(
schema: 'myapp',
httpClient: httpClient,
);
await syncClient.open();
await syncClient.attach('user123');
await syncClient.sync();
// Optional: start default-off automatic downloads.
// Bundle-change watch is only a wake-up hint; pullToStable() remains authoritative.
final automaticDownloads = syncClient.startAutomaticDownloads();
await automaticDownloads.stop();Dart sync package and realserver coverage:
/dart/packages/sqlitenow_oversqlite.
Client-side framework components:
Server-side component:
SQLiteNow Generator and SQLiteNow Library can be used without Oversqlite. Oversqlite can also synchronize a SQLite database with PostgreSQL without using SQLiteNow code generation.
Full documentation is available at https://mobiletoly.github.io/sqlitenow-kmp/.
If SQLiteNow saves you time, please consider starring ⭐ the repository - it helps more KMP, Flutter/Dart, and Swift developers find it.
SQLiteNow is SQL-first tooling for type-safe SQLite access in Kotlin Multiplatform and Flutter/Dart apps, plus native Swift apps through a local generated-package workflow. Write schema, migration, and query files in SQL, then generate platform-native code with typed parameters, typed results, migrations, transactions, and reactive invalidation.
Full documentation is available at https://mobiletoly.github.io/sqlitenow-kmp/.
SQLiteNow keeps SQL as the source of truth while still giving application code a typed API. It is focused exclusively on SQLite instead of abstracting over multiple database engines, which leaves room for SQLite-specific behavior, annotations, migrations, and sync-aware generated code.
The main goals are:
.sql files for schema, migrations, and
queries.-- @@{ queryResult=... }, dynamicField, and mapTo to shape generated
models.SQLiteNow can shape generated result models directly from SQL annotations:
-- @@{ queryResult=PersonWithAddresses }
SELECT p.id,
p.first_name,
p.last_name,
p.email,
p.created_at,
a.address_type,
a.postal_code,
a.country,
a.street,
a.city,
a.state
/* @@{ dynamicField=addresses,
mappingType=collection,
propertyType=List<Address>,
sourceTable=a,
collectionKey=address_id } */
FROM Person p
LEFT JOIN PersonAddress a ON p.id = a.person_id
ORDER BY p.id, a.address_type
LIMIT :limit OFFSET :offsetThis generates a PersonWithAddresses result model with
addresses: List<Address> plus typed query parameters for limit and offset.
Adapters can convert between SQLite values and domain types, and mapTo can
shape data further when the generated model should match application-layer
types.
Full examples are available in /sample-kmp for KMP,
/dart/examples for Dart/Flutter, and
/swift/samples/core for native Swift.
| Platform | Use this when | Status | Start here |
|---|---|---|---|
| Kotlin Multiplatform | You are building shared Kotlin apps for Android, iOS, desktop, JS, or Wasm. | Released Gradle plugin and KMP runtime libraries | KMP guide |
| Flutter/Dart | You are building Flutter apps or pure Dart packages. | Released Dart runtime and CLI packages | Flutter/Dart guide |
| Native Swift local package | You want Swift/Xcode code to consume a generated local SwiftPM package. | Local generated package workflow; release artifacts still in progress | Swift guide |
Supported target details:
macosArm64, linuxX64, linuxArm64,
JVM desktop/server, JavaScript browser, and Kotlin/Wasm browser.package:sqlite3,
plus pure Dart packages through the Dart runtime.Native Swift support currently uses local generated Swift packages on Apple
platforms. Core Swift apps can keep SQL in a Swift/Xcode repository, run the
sqlitenow-generate SwiftPM command plugin, and import the generated package
product. The generator still requires Java 17 or newer, and published runtime
binary artifacts, a Swift-native generator/runtime, and a public SWIFT
compiler backend are not released yet. Native Swift runtime artifacts are
arm64-only: macosArm64, iosArm64, and iosSimulatorArm64.
Each platform has a complete setup guide. The short version is the same across all runtimes: keep SQL in your app repository, configure one or more databases, run the generator, and use the generated API from application code.
Full setup: KMP getting started
KMP apps use the dev.goquick.sqlitenow Gradle plugin and the
dev.goquick.sqlitenow:core runtime dependency. Configure one or more
databases in Gradle:
sqliteNow {
databases {
create("SampleDatabase") {
packageName.set("com.example.app.db")
}
}
}Place SQL files under the matching database directory:
src/commonMain/sql/SampleDatabase/
schema/
queries/
init/
migration/
Generate the database API with the task created from the database name:
./gradlew :composeApp:generateSampleDatabaseReplace :composeApp with your KMP module path and SampleDatabase with your
database name. Generated Kotlin is written under build/generated/sqlitenow/code
and is wired into commonMain by the Gradle plugin. Treat that directory as
generated output: edit SQL/configuration and rerun the task instead of
hand-editing the generated files.
For a complete app walkthrough, follow the Mood Tracker tutorial series and browse the sample project.
Full setup: Flutter/Dart getting started
Flutter and Dart apps use sqlitenow_runtime plus sqlitenow_cli:
dependencies:
sqlitenow_runtime: ^X.Y.Z
dev_dependencies:
sqlitenow_cli: ^X.Y.ZConfigure generation in sqlitenow.yaml:
databases:
AppDatabase:
input: lib/db/sql/AppDatabase
output: lib/db/generated
package: app.db
runtime: dartGenerate Dart code:
flutter pub run sqlitenow_cli generateFor pure Dart packages, use:
dart run sqlitenow_cli generateGenerated Dart is written to the configured output directory, such as
lib/db/generated/app_database.dart. Edit SQL/configuration and regenerate
instead of hand-editing generated Dart.
Full setup: Swift getting started
Swift apps add the released SQLiteNow SwiftPM package to the Swift package that
owns the SQL files, then configure generation with SQLiteNow.json:
{
"schemaVersion": 1,
"databases": [
{
"databaseName": "AppDatabase",
"swiftPackageName": "AppDatabaseSQLiteNow",
"swiftTargetName": "AppDatabaseSQLiteNow",
"runtime": "core"
}
]
}By default, SQL is read from:
SQLiteNow/databases/AppDatabase/
Generate the local Swift package from the Swift package root:
swift package plugin --allow-writing-to-package-directory sqlitenow-generateBy default, the generated package is written to:
SQLiteNowGenerated/AppDatabaseSQLiteNow
Add that generated directory as a local SwiftPM dependency, link the generated product to the app target, and import it from Swift:
import AppDatabaseSQLiteNowTreat SQLiteNowGenerated/ as generated output. Change SQL or
SQLiteNow.json, then rerun sqlitenow-generate.
SQLiteNow includes Oversqlite, an optional synchronization system for multi-device applications with conflict resolution and offline-first behavior. Sync setup requires three explicit pieces on each client:
The sync system automatically handles:
If you have a table to sync, annotate it with enableSync=true:
-- Enable sync for this table
-- @@{ enableSync=true }
CREATE TABLE person (
id TEXT PRIMARY KEY NOT NULL,
first_name TEXT NOT NULL,
last_name TEXT NOT NULL,
email TEXT UNIQUE,
created_at INTEGER NOT NULL DEFAULT (unixepoch())
);Sync-enabled tables must use exactly one local PRIMARY KEY column of type TEXT or BLOB.
INTEGER/BIGINT sync keys are rejected, and local sync-enabled tables must not model the
reserved server scope column _sync_scope_id.
Add the KMP runtime artifacts and enable Oversqlite in the Gradle DSL:
commonMain.dependencies {
implementation("dev.goquick.sqlitenow:core:<version>")
implementation("dev.goquick.sqlitenow:oversqlite:<version>")
}
sqliteNow {
databases {
create("AppDatabase") {
packageName = "com.example.app.db"
oversqlite = true
}
}
}Then use the generated sync client in your application:
// Create authenticated HTTP client with JWT token refresh and base URL
val httpClient = HttpClient {
install(Auth) {
bearer {
loadTokens { /* load saved token */ }
refreshTokens { /* refresh when expired */ }
}
}
defaultRequest {
url("https://api.myapp.com")
}
}
// Create sync client
val syncClient = db.newOversqliteClient(
schema = "myapp",
httpClient = httpClient,
resolver = ServerWinsResolver
)
// Open local runtime and attach the authenticated account.
syncClient.open().getOrThrow()
syncClient.attach(userId = "user123").getOrThrow()
// Perform full sync (upload local changes, download remote changes)
syncClient.sync().getOrThrow()
// Optional: start default-off automatic downloads.
// Bundle-change watch is only a wake-up hint; pullToStable() remains authoritative.
val automaticDownloads = coroutineScope.launch {
syncClient.runAutomaticDownloads(
db.buildOversqliteAutomaticDownloadConfig(
bundleChangeWatchMode = BundleChangeWatchMode.AUTO,
),
)
}
automaticDownloads.cancelAndJoin()KMP sync example: /samplesync-kmp.
Add the Dart runtime packages and enable Oversqlite in sqlitenow.yaml.
Replace X.Y.Z with the latest SQLiteNow release version.
dependencies:
sqlitenow_runtime: ^X.Y.Z
sqlitenow_oversqlite: ^X.Y.Z
dev_dependencies:
sqlitenow_cli: ^X.Y.Zdatabases:
AppDatabase:
input: lib/db/sql/AppDatabase
output: lib/db/generated
package: app.db
runtime: dart
oversqlite: trueThen use the generated sync client in your application:
final httpClient = IoOversqliteHttpClient(
baseUri: Uri.parse('https://api.myapp.com'),
defaultHeaders: {
HttpHeaders.authorizationHeader: 'Bearer $token',
},
);
final syncClient = db.newOversqliteClient(
schema: 'myapp',
httpClient: httpClient,
);
await syncClient.open();
await syncClient.attach('user123');
await syncClient.sync();
// Optional: start default-off automatic downloads.
// Bundle-change watch is only a wake-up hint; pullToStable() remains authoritative.
final automaticDownloads = syncClient.startAutomaticDownloads();
await automaticDownloads.stop();Dart sync package and realserver coverage:
/dart/packages/sqlitenow_oversqlite.
Client-side framework components:
Server-side component:
SQLiteNow Generator and SQLiteNow Library can be used without Oversqlite. Oversqlite can also synchronize a SQLite database with PostgreSQL without using SQLiteNow code generation.
Full documentation is available at https://mobiletoly.github.io/sqlitenow-kmp/.