---
description: Learn how to use Kysely with Capacitor and SQLite to build type-safe database layers with a fluent query builder in your mobile apps.
title: How to Use Kysely with Capacitor and SQLite - Capawesome
image: https://capawesome.io/docs/assets/images/social/blog/how-to-use-kysely-with-capacitor-and-sqlite.png
---

[ Skip to content](#how-to-use-kysely-with-capacitor-and-sqlite) 

[ 🔐 Introducing the **Capacitor Vault** plugin — store secrets behind biometrics or a device passcode.](/blog/announcing-the-capacitor-vault-plugin/) 

* [  SDKs ](/docs/sdks/)
* [  Formbricks ](/docs/sdks/capacitor/formbricks/)
* [  Geocoder ](/docs/sdks/capacitor/geocoder/)
* [  Google Sign-In ](/docs/sdks/capacitor/google-sign-in/)
* [  Grafana Faro ](/docs/sdks/capacitor/grafana-faro/)
* [  libSQL ](/docs/sdks/capacitor/libsql/)
* [  Live Update ](/docs/sdks/capacitor/live-update/)
* [  Managed Configurations ](/docs/sdks/capacitor/managed-configurations/)
* [  Media Session ](/docs/sdks/capacitor/media-session/)
* [  ML Kit ](/docs/sdks/capacitor/mlkit/)
* [  Navigation Bar ](/docs/sdks/capacitor/navigation-bar/)
* [  NFC ](/docs/sdks/capacitor/nfc/)
* [  OAuth ](/docs/sdks/capacitor/oauth/)
* [  Pedometer ](/docs/sdks/capacitor/pedometer/)
* [  Photo Editor ](/docs/sdks/capacitor/photo-editor/)
* [  PostHog ](/docs/sdks/capacitor/posthog/)
* [  Printer ](/docs/sdks/capacitor/printer/)
* [  Purchases ](/docs/sdks/capacitor/purchases/)
* [  RealtimeKit ](/docs/sdks/capacitor/realtimekit/)
* [  Screen Orientation ](/docs/sdks/capacitor/screen-orientation/)
* [  Screenshot ](/docs/sdks/capacitor/screenshot/)
* [  Secure Preferences ](/docs/sdks/capacitor/secure-preferences/)
* [  Speech Recognition ](/docs/sdks/capacitor/speech-recognition/)
* [  Speech Synthesis ](/docs/sdks/capacitor/speech-synthesis/)
* [  Share Target ](/docs/sdks/capacitor/share-target/)
* [  Square Mobile Payments ](/docs/sdks/capacitor/square-mobile-payments/)
* [  SQLite ](/docs/sdks/capacitor/sqlite/)
* [  Superwall ](/docs/sdks/capacitor/superwall/)
* [  Torch ](/docs/sdks/capacitor/torch/)
* [  Vault ](/docs/sdks/capacitor/vault/)
* [  Wifi ](/docs/sdks/capacitor/wifi/)
* [  Zip ](/docs/sdks/capacitor/zip/)
* [  Cordova ](/docs/sdks/cordova/)
* [  Cloud ](/docs/cloud/)
* [  Integrations ](/docs/cloud/live-updates/integrations/)
* Concepts
* Reference
* [  Troubleshooting ](/docs/cloud/live-updates/troubleshooting/)
* [  FAQ ](/docs/cloud/live-updates/faq/)
* [  Native Builds ](/docs/cloud/native-builds/)
* [  Set Up Environments ](/docs/cloud/native-builds/environments/)
* [  Overwrite Native Configurations ](/docs/cloud/native-builds/native-configurations/)
* [  Auto-Increment Build Numbers ](/docs/cloud/native-builds/auto-incrementing-build-numbers/)
* [  Configure the Web Build Script ](/docs/cloud/native-builds/web-build-script/)
* [  Build from a Monorepo ](/docs/cloud/native-builds/monorepo/)
* [  Use pnpm or Yarn ](/docs/cloud/native-builds/package-managers/)
* [  Install Private npm Packages ](/docs/cloud/native-builds/npm-private-registry/)
* [  Override the Java Version ](/docs/cloud/native-builds/override-java-version/)
* [  Custom iOS Provisioning Profiles ](/docs/cloud/native-builds/custom-ios-provisioning-profiles/)
* [  Build without Git ](/docs/cloud/native-builds/build-without-git/)
* [  Access Git Behind a Firewall ](/docs/cloud/native-builds/firewall-access/)
* [  Integrations ](/docs/cloud/native-builds/integrations/)
* Reference
* [  Troubleshooting ](/docs/cloud/native-builds/troubleshooting/)
* [  FAQ ](/docs/cloud/native-builds/faq/)
* [  App Store Publishing ](/docs/cloud/app-store-publishing/)
* [  Submit a Build ](/docs/cloud/app-store-publishing/submit-a-build/)
* [  Submit Automatically After a Build ](/docs/cloud/app-store-publishing/submit-automatically/)
* [  Troubleshooting ](/docs/cloud/app-store-publishing/troubleshooting/)
* [  FAQ ](/docs/cloud/app-store-publishing/faq/)
* [  Automations ](/docs/cloud/automations/)
* [  Reference ](/docs/cloud/automations/reference/)
* [  Troubleshooting ](/docs/cloud/automations/troubleshooting/)
* [  FAQ ](/docs/cloud/automations/faq/)
* [  Assist ](/docs/cloud/assist/)
* [  CLI ](/docs/cloud/cli/)
* APIs and SDKs
* [  Webhooks ](/docs/cloud/webhooks/)
* [  Integrations ](/docs/cloud/integrations/)
* Account
* [  Organization ](/docs/cloud/organizations/)
* [  Two-Factor Enforcement ](/docs/cloud/organizations/two-factor-authentication/)
* [  Audit Logs ](/docs/cloud/organizations/audit-logs/)
* [  Billing ](/docs/cloud/organizations/billing/)
* [  License Keys ](/docs/cloud/license-keys/)
* [  AI ](/docs/ai/)
* [  Insiders ](/docs/insiders/)
* [  Billing & Plans ](/docs/insiders/billing-and-plans/)
* [  FAQ ](/docs/insiders/faq/)
* [  License ](https://capawesome.io/legal/eula/)
* [  Support ](/docs/support/)
* [  Contributing ](/docs/contributing/)
* Contributing code
* [  Code of Conduct ](/docs/contributing/code-of-conduct/)
* [  Questions ](https://docs.github.com/en/discussions/collaborating-with-your-community-using-discussions/participating-in-a-discussion#creating-a-discussion)
* [  Blog ](/blog/)
* Categories

* [  Transactions ](#transactions)
* [  Migrations ](#migrations)
* [  Stay Updated ](#stay-updated)
* [  Conclusion ](#conclusion)

* Related links

# How to Use Kysely with Capacitor and SQLite[¶](#how-to-use-kysely-with-capacitor-and-sqlite "Permanent link")

Working with raw SQL in a Capacitor app gets messy fast — queries are just strings, results are untyped, and refactoring a column name means hunting through your entire codebase. Kysely solves this with a type-safe query builder that catches errors at compile time while keeping you close to SQL. In this guide, you'll learn how to set up Kysely with the [Capacitor SQLite plugin](/docs/sdks/capacitor/sqlite/) using the new `@capawesome/capacitor-sqlite-kysely` dialect.

For the **Capacitor SQLite plugin** API, see the [plugin documentation](/docs/sdks/capacitor/sqlite/#api).

[ ![Build and deploy your Capacitor app with Capawesome Cloud](../../assets/external/cloud.capawesome.io/assets/banners/cloud-build-and-deploy-capacitor-apps.69628c3f.png) ](/) 

## What is Kysely?[¶](#what-is-kysely "Permanent link")

[Kysely](https://kysely.dev/) (pronounced "Key-seh-lee") is a type-safe TypeScript SQL query builder. It's not a traditional ORM that hides SQL behind abstract methods — instead, it gives you a fluent API that maps directly to SQL, with full type inference at every step.

Here's what makes it a good fit for Capacitor apps:

* **Type safety** — Queries are validated against your database types at compile time. If you reference a column that doesn't exist, TypeScript catches it before the code runs.
* **SQL-first** — The API mirrors SQL syntax closely. If you know `SELECT`, `WHERE`, `JOIN`, and `INSERT`, you already know how to use Kysely.
* **Dialect system** — Kysely uses a pluggable dialect architecture, making it straightforward to integrate with different database backends — including Capacitor SQLite.
* **Built-in migrations** — Kysely includes a `Migrator` class that lets you define and run migrations in TypeScript. No external tooling required.
* **Lightweight** — Kysely has no runtime dependencies and a small footprint, which keeps your app bundle lean.

## Prerequisites[¶](#prerequisites "Permanent link")

Before you begin, make sure you have a Capacitor project with the [Capacitor SQLite plugin](/docs/sdks/capacitor/sqlite/) installed. To install the plugin, please refer to the [Installation](/docs/sdks/capacitor/sqlite/#installation) section in the plugin documentation.

## Installation[¶](#installation "Permanent link")

Install the Kysely dialect along with Kysely itself:

`[](#%5F%5Fcodelineno-0-1)npm install @capawesome/capacitor-sqlite-kysely kysely
`

## Setting Up the Database[¶](#setting-up-the-database "Permanent link")

To get started, open a database using the [Capacitor SQLite plugin](/docs/sdks/capacitor/sqlite/) and create a Kysely instance with the `CapacitorSqliteDialect`:

`[](#%5F%5Fcodelineno-1-1)import { Sqlite } from '@capawesome-team/capacitor-sqlite';
[](#%5F%5Fcodelineno-1-2)import { Kysely } from 'kysely';
[](#%5F%5Fcodelineno-1-3)import { CapacitorSqliteDialect } from '@capawesome/capacitor-sqlite-kysely';
[](#%5F%5Fcodelineno-1-4)
[](#%5F%5Fcodelineno-1-5)const { databaseId } = await Sqlite.open({ path: 'my.db' });
[](#%5F%5Fcodelineno-1-6)const db = new Kysely<Database>({
[](#%5F%5Fcodelineno-1-7)  dialect: new CapacitorSqliteDialect(Sqlite, { databaseId }),
[](#%5F%5Fcodelineno-1-8)});
`

The `CapacitorSqliteDialect` takes two arguments: the `Sqlite` plugin instance and a configuration object with the `databaseId` returned by [open(...)](/docs/sdks/capacitor/sqlite/#open). The `Database` generic parameter is a TypeScript interface that describes your tables — we'll define that next.

## Defining Your Database Types[¶](#defining-your-database-types "Permanent link")

Kysely uses TypeScript interfaces to describe your database schema. This is what powers its type inference — every query you write is checked against these types at compile time.

Create a types file for your database:

`[](#%5F%5Fcodelineno-2-1)import { Generated } from 'kysely';
[](#%5F%5Fcodelineno-2-2)
[](#%5F%5Fcodelineno-2-3)interface Database {
[](#%5F%5Fcodelineno-2-4)  users: UsersTable;
[](#%5F%5Fcodelineno-2-5)  posts: PostsTable;
[](#%5F%5Fcodelineno-2-6)}
[](#%5F%5Fcodelineno-2-7)
[](#%5F%5Fcodelineno-2-8)interface UsersTable {
[](#%5F%5Fcodelineno-2-9)  id: Generated<number>;
[](#%5F%5Fcodelineno-2-10)  name: string;
[](#%5F%5Fcodelineno-2-11)  email: string;
[](#%5F%5Fcodelineno-2-12)}
[](#%5F%5Fcodelineno-2-13)
[](#%5F%5Fcodelineno-2-14)interface PostsTable {
[](#%5F%5Fcodelineno-2-15)  id: Generated<number>;
[](#%5F%5Fcodelineno-2-16)  title: string;
[](#%5F%5Fcodelineno-2-17)  content: string | null;
[](#%5F%5Fcodelineno-2-18)  author_id: number;
[](#%5F%5Fcodelineno-2-19)}
`

A few things to note here:

* Each key in the `Database` interface corresponds to a table name in your database.
* `Generated<number>` marks a column as auto-generated (e.g. an auto-incrementing primary key). Kysely will make this column optional in `INSERT` statements but required in `SELECT` results.
* Nullable columns use a union type with `null` (e.g. `string | null`).
* These types don't create tables — they only describe the shape of your data for TypeScript's type checker.

## Running Queries[¶](#running-queries "Permanent link")

With the database types in place, Kysely gives you a fluent, chainable API for building SQL queries. Every query is fully typed based on your `Database` interface.

### Insert[¶](#insert "Permanent link")

`[](#%5F%5Fcodelineno-3-1)await db
[](#%5F%5Fcodelineno-3-2)  .insertInto('users')
[](#%5F%5Fcodelineno-3-3)  .values({ name: 'Alice', email: 'alice@example.com' })
[](#%5F%5Fcodelineno-3-4)  .execute();
`

### Select[¶](#select "Permanent link")

`[](#%5F%5Fcodelineno-4-1)// Select all users
[](#%5F%5Fcodelineno-4-2)const allUsers = await db.selectFrom('users').selectAll().execute();
[](#%5F%5Fcodelineno-4-3)
[](#%5F%5Fcodelineno-4-4)// Select with a filter
[](#%5F%5Fcodelineno-4-5)const user = await db
[](#%5F%5Fcodelineno-4-6)  .selectFrom('users')
[](#%5F%5Fcodelineno-4-7)  .selectAll()
[](#%5F%5Fcodelineno-4-8)  .where('email', '=', 'alice@example.com')
[](#%5F%5Fcodelineno-4-9)  .executeTakeFirst();
`

The `executeTakeFirst()` method returns a single result or `undefined`, which is useful when you expect at most one row.

### Update[¶](#update "Permanent link")

`[](#%5F%5Fcodelineno-5-1)await db
[](#%5F%5Fcodelineno-5-2)  .updateTable('users')
[](#%5F%5Fcodelineno-5-3)  .set({ name: 'Bob' })
[](#%5F%5Fcodelineno-5-4)  .where('id', '=', 1)
[](#%5F%5Fcodelineno-5-5)  .execute();
`

### Delete[¶](#delete "Permanent link")

`[](#%5F%5Fcodelineno-6-1)await db
[](#%5F%5Fcodelineno-6-2)  .deleteFrom('users')
[](#%5F%5Fcodelineno-6-3)  .where('id', '=', 1)
[](#%5F%5Fcodelineno-6-4)  .execute();
`

Every query is validated at compile time. If you mistype a column name or pass the wrong type, TypeScript will flag it immediately.

## Transactions[¶](#transactions "Permanent link")

For operations that need to succeed or fail atomically, use transactions. Kysely manages `BEGIN`, `COMMIT`, and `ROLLBACK` automatically:

`[](#%5F%5Fcodelineno-7-1)await db.transaction().execute(async (trx) => {
[](#%5F%5Fcodelineno-7-2)  await trx
[](#%5F%5Fcodelineno-7-3)    .insertInto('users')
[](#%5F%5Fcodelineno-7-4)    .values({ name: 'Alice', email: 'alice@example.com' })
[](#%5F%5Fcodelineno-7-5)    .execute();
[](#%5F%5Fcodelineno-7-6)  await trx
[](#%5F%5Fcodelineno-7-7)    .insertInto('posts')
[](#%5F%5Fcodelineno-7-8)    .values({ title: 'Hello World', content: '...', author_id: 1 })
[](#%5F%5Fcodelineno-7-9)    .execute();
[](#%5F%5Fcodelineno-7-10)});
`

If any statement inside the callback throws an error, the entire transaction is rolled back. This is essential for maintaining data consistency when inserting related records across multiple tables.

## Migrations[¶](#migrations "Permanent link")

Kysely includes a built-in `Migrator` class for managing database schema changes. Since the `CapacitorSqliteDialect` implements Kysely's standard `Dialect` interface, migrations work out of the box — no extra tooling or bundler plugins needed.

Define your migrations as a `MigrationProvider`:

`[](#%5F%5Fcodelineno-8-1)import { Kysely, Migrator, MigrationProvider } from 'kysely';
[](#%5F%5Fcodelineno-8-2)
[](#%5F%5Fcodelineno-8-3)const migrationProvider: MigrationProvider = {
[](#%5F%5Fcodelineno-8-4)  async getMigrations() {
[](#%5F%5Fcodelineno-8-5)    return {
[](#%5F%5Fcodelineno-8-6)      '001_create_users': {
[](#%5F%5Fcodelineno-8-7)        async up(db: Kysely<any>) {
[](#%5F%5Fcodelineno-8-8)          await db.schema
[](#%5F%5Fcodelineno-8-9)            .createTable('users')
[](#%5F%5Fcodelineno-8-10)            .addColumn('id', 'integer', (col) => col.primaryKey().autoIncrement())
[](#%5F%5Fcodelineno-8-11)            .addColumn('name', 'text', (col) => col.notNull())
[](#%5F%5Fcodelineno-8-12)            .addColumn('email', 'text', (col) => col.notNull().unique())
[](#%5F%5Fcodelineno-8-13)            .execute();
[](#%5F%5Fcodelineno-8-14)        },
[](#%5F%5Fcodelineno-8-15)      },
[](#%5F%5Fcodelineno-8-16)      '002_create_posts': {
[](#%5F%5Fcodelineno-8-17)        async up(db: Kysely<any>) {
[](#%5F%5Fcodelineno-8-18)          await db.schema
[](#%5F%5Fcodelineno-8-19)            .createTable('posts')
[](#%5F%5Fcodelineno-8-20)            .addColumn('id', 'integer', (col) => col.primaryKey().autoIncrement())
[](#%5F%5Fcodelineno-8-21)            .addColumn('title', 'text', (col) => col.notNull())
[](#%5F%5Fcodelineno-8-22)            .addColumn('content', 'text')
[](#%5F%5Fcodelineno-8-23)            .addColumn('author_id', 'integer', (col) => col.notNull().references('users.id'))
[](#%5F%5Fcodelineno-8-24)            .execute();
[](#%5F%5Fcodelineno-8-25)        },
[](#%5F%5Fcodelineno-8-26)      },
[](#%5F%5Fcodelineno-8-27)    };
[](#%5F%5Fcodelineno-8-28)  },
[](#%5F%5Fcodelineno-8-29)};
`

Then apply the migrations when your app starts:

`[](#%5F%5Fcodelineno-9-1)const migrator = new Migrator({ db, provider: migrationProvider });
[](#%5F%5Fcodelineno-9-2)const { error, results } = await migrator.migrateToLatest();
[](#%5F%5Fcodelineno-9-3)
[](#%5F%5Fcodelineno-9-4)if (error) {
[](#%5F%5Fcodelineno-9-5)  console.error('Migration failed:', error);
[](#%5F%5Fcodelineno-9-6)}
`

Migrations are written in TypeScript using Kysely's schema builder, which means they benefit from the same type safety and autocompletion as your queries. The `Migrator` tracks applied migrations automatically, so calling `migrateToLatest()` multiple times is safe — only pending migrations are executed.

## Stay Updated[¶](#stay-updated "Permanent link")

Want to stay up to date with the latest features and guides? Subscribe to the Capawesome newsletter.

[Subscribe to the Capawesome Newsletter](/newsletter/)

## Conclusion[¶](#conclusion "Permanent link")

With the `@capawesome/capacitor-sqlite-kysely` dialect, you can use Kysely's type-safe query builder and built-in migration system directly in your Capacitor apps. The setup is minimal: define your database types as TypeScript interfaces, create a dialect instance, and start writing queries that are checked at compile time — all while staying close to SQL.

**Resources:**

* For the full API reference and source code, visit the [Adapter on GitHub](https://github.com/capawesome-team/capacitor-sqlite-drivers/tree/main/packages/kysely)

**Related tutorials:**

* If you're looking for an alternative approach with schema-as-code and relational queries, check out our guide on [How to Use Drizzle ORM with Capacitor and SQLite](/blog/how-to-use-drizzle-orm-with-capacitor-and-sqlite/)
* For a decorator-based ORM check [TypeORM with Capacitor and SQLite](/blog/how-to-use-typeorm-with-capacitor-and-sqlite/)

If you have questions or feedback, join the [Capawesome Discord](https://discord.gg/VCXxSVjefW) server to connect with the community. And subscribe to the Capawesome [newsletter](/newsletter/) to stay updated on the latest news.

June 8, 2026 

 Back to top 