If SQLiteNow saves you time, please consider starring ⭐ the repository - it helps more KMP and Flutter/Dart developers find it.
SQLiteNow is SQL-first tooling for type-safe SQLite access in Kotlin Multiplatform and Flutter/Dart apps. Write schema, migration, and query files in SQL, then generate platform-native code with typed parameters, typed results, migrations, transactions, and reactive invalidation.
Kotlin Multiplatform apps use the dev.goquick.sqlitenow Gradle plugin and KMP
runtime libraries.
Flutter and Dart apps use sqlitenow_runtime plus sqlitenow_cli. You might see a
little documentation bias toward KMP at first, because originally project was started
as KMP-only, but eventually it will have a full docs parity.
Sync-Ready: SQLiteNow includes Oversqlite, a synchronization system for multi-device applications with conflict resolution and offline-first behavior.
SQLiteNow generates code from your SQL files, giving you full control over your queries while maintaining type safety. Unlike SQLDelight (for KMP), which supports multiple database engines, SQLiteNow is focused exclusively on SQLite, allowing for deeper integration and SQLite-specific optimizations.
Full documentation is available at https://mobiletoly.github.io/sqlitenow-kmp/.
[plugins]
sqlitenow = { id = "dev.goquick.sqlitenow", version = "0.9.1" }
[libraries]
sqlitenow-core = { module = "dev.goquick.sqlitenow:core", version = "0.9.1" }sqliteNow {
databases {
create("SampleDatabase") {
packageName.set("com.example.app.db")
}
}
}Start here: https://mobiletoly.github.io/sqlitenow-kmp/kmp/
While we have added includes few sample projects to this repository, but for a very simple end-to-end walkthrough, follow the Mood Tracker tutorial series and browse the accompanying sample project.
dependencies:
sqlitenow_runtime: ^0.9.0
dev_dependencies:
sqlitenow_cli: ^0.9.0flutter pub run sqlitenow_cli generateFor pure Dart packages, use:
dart run sqlitenow_cli generateThe released Dart CLI package embeds the SQLiteNow compiler jar. Flutter and
Dart users do not pass --compiler-jar for normal consumption.
Start here: https://mobiletoly.github.io/sqlitenow-kmp/flutter/
- Android (Kotlin/JVM via AndroidX bundled SQLite driver)
- iOS (Kotlin/Native with bundled SQLite)
- macOS native (
macosArm64,macosX64) with bundled SQLite - Linux native (
linuxX64,linuxArm64) with bundled SQLite - JVM desktop/server targets
- JavaScript (browser) via SQL.js with optional IndexedDB persistence
- Kotlin/Wasm (browser) using the same SQL.js runtime with automatic OPFS or IndexedDB persistence
- Flutter native runtimes through Dart VM and
package:sqlite3
- Pure SQL Control - Write your queries in SQL files, get type-safe generated code
- Comment-based Annotations - Control code generation using simple
-- @@{ annotations }comments in your SQL. - No IDE Plugin Required - Works with any editor
- Multiple Consumption Paths - Flutter/Dart packages use the Dart CLI; KMP apps use the Gradle plugin
- SQLite Focused - Optimized specifically for SQLite features and capabilities
- Migration support - Migration scripts are supported to manage database schema changes
- Built-in Sync System - Complete synchronization solution for multi-device applications
- Conflict Resolution - Automatic conflict resolution with pluggable strategies (Server Wins, Client Wins, etc.)
- Change Tracking - Automatic tracking of INSERT, UPDATE, DELETE operations
- Offline-First - Works seamlessly offline, syncs when connection is available
- JWT Authentication - Secure sync with customizable authentication via HttpClient
- Incremental Sync - Efficient sync with pagination and change-based updates
Client-side framework components:
- SQLiteNow Generator - The code generation component of the SQLiteNow framework that generates type-safe Kotlin or Dart code from SQL files.
- SQLiteNow Library - The core library that provides convenient APIs for database access.
- OverSqlite - The sync component of the SQLiteNow framework that enables seamless data sharing across multiple devices with automatic conflict resolution, change tracking, and offline-first capabilities.
Server-side components:
- OverSync - Sync server that provides an adapter library for data synchronization. Currently we have go-oversync implementation in Go with PostgreSQL as data store. Visit this link for more information: https://github.com/mobiletoly/go-oversync
It is important to mention that you can use SQLiteNow Generator and SQLiteNow Library without using OverSqlite for synchronization. And vice versa - you can use OverSqlite for synchronization of SQLite database with PostgreSQL without using SQLiteNow Generator and SQLiteNow Library.
Even if you don't care about multi-device synchronization, SQLiteNow has few key differences from SQLDelight:
First of all, I wanted to target specifically SQLite in Kotlin Multiplatform and Dart environments, these are my platform of choice as of now for mobile development.
Second, since we use comment-based annotations, no plugin is required (like in case of SQLDelight), so you can easily use just a regular SQL files that will be validated by your IDE or other external tools for correctness.
Third, I wanted to have a more flexible and extensible code generation system that can be easily extended and customized. I use hexagonal architecture in my code, but sometimes converting between multiple layers is tiresome, so I wanted to have a way to generate code that will be very close to my domain layer, without sacrificing the ability to write pure SQL queries.
Here is the brief example:
-- @@{ 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 :offset
This will generate PersonWithAddresses data class. This class has addresses: List<Address>
property that contains all home addresses for the person. Another class
PersonQuery.SelectAllWithAddresses.Params will be generated as well with limit and offset
parameters to pass parameters to the query.
And you can define your own adapters to convert between SQLite and your domain types and register them for seamless integration. We provide few built-in adapters as well, such as converting from TEXT to Kotlin's date/time etc.
You can always shape your data even more with mapTo annotation.
Full example is available in the /sample-kmp directory (for KMP) and
in [/dart/examples] for Dart/Flutter.
SQLiteNow includes a complete synchronization system for building multi-device applications. Sync setup requires three explicit pieces on each client:
- the SQLiteNow runtime package
- the Oversqlite runtime package
- generated database configuration with Oversqlite enabled
The sync system automatically handles:
- Change tracking for all sync-enabled tables
- Conflict resolution for concurrent writes across devices
- Incremental sync to minimize bandwidth usage
- Chunked push upload for large dirty sets without a total-row hard ceiling
- Error handling and retry logic
- Authentication via customizable HTTP clients
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()KMP sync example: /samplesync-kmp.
Add the Dart runtime packages and enable Oversqlite in sqlitenow.yaml:
dependencies:
sqlitenow_runtime: ^0.9.0
sqlitenow_oversqlite: ^0.9.0
dev_dependencies:
sqlitenow_cli: ^0.9.0databases:
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();Dart sync package and realserver coverage: /dart/packages/sqlitenow_oversqlite.
Full documentation is available in the https://mobiletoly.github.io/sqlitenow-kmp/