---
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
---

<!doctype html> 

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

[🖥️ Introducing the **Capacitor Electron Platform** — build desktop apps for macOS, Windows, and Linux. Free & open source. ](/blog/announcing-the-capacitor-electron-platform/) 

* [ 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/)
* [ Gyroscope ](/docs/sdks/capacitor/gyroscope/)
* [ Haptics ](/docs/sdks/capacitor/haptics/)
* [ Home Indicator ](/docs/sdks/capacitor/home-indicator/)
* [ In-App Browser ](/docs/sdks/capacitor/in-app-browser/)
* [ Install Referrer ](/docs/sdks/capacitor/install-referrer/)
* [ Intercom ](/docs/sdks/capacitor/intercom/)
* [ Intune ](/docs/sdks/capacitor/intune/)
* [ Keep Awake ](/docs/sdks/capacitor/keep-awake/)
* [ libSQL ](/docs/sdks/capacitor/libsql/)
* [ Light Sensor ](/docs/sdks/capacitor/light-sensor/)
* [ Live Update ](/docs/sdks/capacitor/live-update/)
* [ Localization ](/docs/sdks/capacitor/localization/)
* [ Mail Composer ](/docs/sdks/capacitor/mail-composer/)
* [ Managed Configurations ](/docs/sdks/capacitor/managed-configurations/)
* [ Maps Launcher ](/docs/sdks/capacitor/maps-launcher/)
* [ Media Session ](/docs/sdks/capacitor/media-session/)
* [ ML Kit ](/docs/sdks/capacitor/mlkit/)
* [ Navigation Bar ](/docs/sdks/capacitor/navigation-bar/)
* [ Network ](/docs/sdks/capacitor/network/)
* [ NFC ](/docs/sdks/capacitor/nfc/)
* [ Node.js ](/docs/sdks/capacitor/nodejs/)
* [ OAuth ](/docs/sdks/capacitor/oauth/)
* [ Passkeys ](/docs/sdks/capacitor/passkeys/)
* [ Password Autofill ](/docs/sdks/capacitor/password-autofill/)
* [ PDF Generator ](/docs/sdks/capacitor/pdf-generator/)
* [ PDF Viewer ](/docs/sdks/capacitor/pdf-viewer/)
* [ Pedometer ](/docs/sdks/capacitor/pedometer/)
* [ Permissions ](/docs/sdks/capacitor/permissions/)
* [ Phone Dialer ](/docs/sdks/capacitor/phone-dialer/)
* [ Photo Editor ](/docs/sdks/capacitor/photo-editor/)
* [ Photo Manipulator ](/docs/sdks/capacitor/photo-manipulator/)
* [ PixLive ](/docs/sdks/capacitor/pixlive/)
* [ PostHog ](/docs/sdks/capacitor/posthog/)
* [ Printer ](/docs/sdks/capacitor/printer/)
* [ Privacy Screen ](/docs/sdks/capacitor/privacy-screen/)
* [ Proximity Sensor ](/docs/sdks/capacitor/proximity-sensor/)
* [ Purchases ](/docs/sdks/capacitor/purchases/)
* [ RealtimeKit ](/docs/sdks/capacitor/realtimekit/)
* [ Root Detection ](/docs/sdks/capacitor/root-detection/)
* [ Screen Brightness ](/docs/sdks/capacitor/screen-brightness/)
* [ Screen Orientation ](/docs/sdks/capacitor/screen-orientation/)
* [ Screen Reader ](/docs/sdks/capacitor/screen-reader/)
* [ Screenshot ](/docs/sdks/capacitor/screenshot/)
* [ Secure Preferences ](/docs/sdks/capacitor/secure-preferences/)
* [ Settings Launcher ](/docs/sdks/capacitor/settings-launcher/)
* [ Shake ](/docs/sdks/capacitor/shake/)
* [ Silent Mode ](/docs/sdks/capacitor/silent-mode/)
* [ SIM ](/docs/sdks/capacitor/sim/)
* [ SMS Composer ](/docs/sdks/capacitor/sms-composer/)
* [ 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/)
* [ System WebView ](/docs/sdks/capacitor/system-webview/)
* [ Tauri ](/docs/sdks/capacitor/tauri/)
* [ Text Interaction ](/docs/sdks/capacitor/text-interaction/)
* [ Text Zoom ](/docs/sdks/capacitor/text-zoom/)
* [ Thermal State ](/docs/sdks/capacitor/thermal-state/)
* [ Toast ](/docs/sdks/capacitor/toast/)
* [ Torch ](/docs/sdks/capacitor/torch/)
* [ Vault ](/docs/sdks/capacitor/vault/)
* [ Volume ](/docs/sdks/capacitor/volume/)
* [ Wallet ](/docs/sdks/capacitor/wallet/)
* [ Wifi ](/docs/sdks/capacitor/wifi/)
* [ YouTube Player ](/docs/sdks/capacitor/youtube-player/)
* [ 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, Yarn, or bun ](/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)
* [ FAQ ](#faq)
* [ 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](https://capawesome.io/assets/banners/cloud-build-and-deploy-capacitor-apps.png?t=1) ](/) 

## 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.

## FAQ[¶](#faq "Permanent link")

### Do Kysely migrations need any extra bundler configuration in a Capacitor app, unlike other ORMs?[¶](#do-kysely-migrations-need-any-extra-bundler-configuration-in-a-capacitor-app-unlike-other-orms "Permanent link")

No — this is one of the practical advantages of Kysely's approach here. Because the `CapacitorSqliteDialect` implements Kysely's standard `Dialect` interface, migrations are just TypeScript code using the schema builder; there's no generated `.sql` file bundling step or bundler plugin needed, unlike setups that rely on importing raw `.sql` files as strings.

### What does `Generated<number>` actually change about how I use a column?[¶](#what-does-generatednumber-actually-change-about-how-i-use-a-column "Permanent link")

It changes whether the column is required in `insertInto()` calls versus `selectFrom()` results. A `Generated<number>` primary key becomes optional when inserting (since the database assigns it), but Kysely still types it as present and required on every row you read back — this is purely a TypeScript-level distinction, not something that affects the actual SQL generated.

### Is Kysely closer to a query builder or a full ORM like TypeORM?[¶](#is-kysely-closer-to-a-query-builder-or-a-full-orm-like-typeorm "Permanent link")

A query builder, not an ORM in the traditional sense. Kysely doesn't hide SQL behind entity classes or a repository pattern — its API mirrors SQL syntax directly (`selectFrom`, `where`, `insertInto`), so you're still thinking in terms of tables and columns rather than object relationships. If you want decorator-based entities and automatic relationship loading instead, that's what TypeORM's repository pattern provides.

### What happens if `migrateToLatest()` is called on every app start?[¶](#what-happens-if-migratetolatest-is-called-on-every-app-start "Permanent link")

It's safe — the `Migrator` tracks which migrations have already run and only executes pending ones. Repeated calls across app launches won't reapply migrations that already succeeded, so wiring `migrateToLatest()` into your app's startup sequence is the intended usage pattern, not something you need to guard with your own "have migrations run" check.

### Do I need to define relationships in my `Database` interface for joins to work?[¶](#do-i-need-to-define-relationships-in-my-database-interface-for-joins-to-work "Permanent link")

Not the way a relational-query ORM requires it. Kysely's `Database` interface only describes column shapes and types per table — foreign key relationships (like `posts.author_id` referencing `users.id`) are expressed in migrations via `.references()`, but querying across tables still means writing an explicit `.innerJoin()` or similar in your query, rather than declaring the relationship once and having Kysely traverse it automatically.

## 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.

July 17, 2026 

Back to top

```json
{
      "@context": "https://schema.org",
      "@type": "BlogPosting",
      "headline": "How to Use Kysely with Capacitor and SQLite",
      "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.",
      "image": "https://capawesome.io/assets/banners/cloud-build-and-deploy-capacitor-apps.png",
      "datePublished": "2026-02-26T00:00:00+00:00",
      "dateModified": "2026-07-17T00:00:00+00:00",
      "author": [
        {
          "@type": "Person",
          "name": "Robin Genz",
          "url": "https://github.com/robingenz"
        }
      ],
      "publisher": {
        "@type": "Organization",
        "name": "Capawesome",
        "url": "https://capawesome.io",
        "logo": {
          "@type": "ImageObject",
          "url": "https://capawesome.io/assets/images/logo.svg"
        }
      },
      "articleSection": "Capacitor",
      "keywords": ["Capacitor", "Guides", "SDKs"],
      "isPartOf": {
        "@type": "Blog",
        "@id": "https://capawesome.io/blog/#blog"
      },
      "mainEntityOfPage": "https://capawesome.io/blog/how-to-use-kysely-with-capacitor-and-sqlite/",
      "url": "https://capawesome.io/blog/how-to-use-kysely-with-capacitor-and-sqlite/"
    }
{
      "@context": "https://schema.org",
      "@type": "BreadcrumbList",
      "itemListElement": [
        {
          "@type": "ListItem",
          "position": 1,
          "name": "Home",
          "item": "https://capawesome.io/"
        },
        {
          "@type": "ListItem",
          "position": 2,
          "name": "Blog",
          "item": "https://capawesome.io/blog/"
        },
        {
          "@type": "ListItem",
          "position": 3,
          "name": "How to Use Kysely with Capacitor and SQLite",
          "item": "https://capawesome.io/blog/how-to-use-kysely-with-capacitor-and-sqlite/"
        }
      ]
    }
{"@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [{"@type": "Question", "name": "What is Kysely?", "acceptedAnswer": {"@type": "Answer", "text": "Kysely (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."}}, {"@type": "Question", "name": "Do Kysely migrations need any extra bundler configuration in a Capacitor app, unlike other ORMs?", "acceptedAnswer": {"@type": "Answer", "text": "No — this is one of the practical advantages of Kysely's approach here. Because the CapacitorSqliteDialect implements Kysely's standard Dialect interface, migrations are just TypeScript code using the schema builder; there's no generated.sql file bundling step or bundler plugin needed, unlike setups that rely on importing raw.sql files as strings."}}, {"@type": "Question", "name": "What does Generated<number> actually change about how I use a column?", "acceptedAnswer": {"@type": "Answer", "text": "It changes whether the column is required in insertInto() calls versus selectFrom() results. A Generated<number> primary key becomes optional when inserting (since the database assigns it), but Kysely still types it as present and required on every row you read back — this is purely a TypeScript-level distinction, not something that affects the actual SQL generated."}}, {"@type": "Question", "name": "Is Kysely closer to a query builder or a full ORM like TypeORM?", "acceptedAnswer": {"@type": "Answer", "text": "A query builder, not an ORM in the traditional sense. Kysely doesn't hide SQL behind entity classes or a repository pattern — its API mirrors SQL syntax directly ( selectFrom, where, insertInto), so you're still thinking in terms of tables and columns rather than object relationships. If you want decorator-based entities and automatic relationship loading instead, that's what TypeORM's repository pattern provides."}}, {"@type": "Question", "name": "What happens if migrateToLatest() is called on every app start?", "acceptedAnswer": {"@type": "Answer", "text": "It's safe — the Migrator tracks which migrations have already run and only executes pending ones. Repeated calls across app launches won't reapply migrations that already succeeded, so wiring migrateToLatest() into your app's startup sequence is the intended usage pattern, not something you need to guard with your own \"have migrations run\" check."}}, {"@type": "Question", "name": "Do I need to define relationships in my Database interface for joins to work?", "acceptedAnswer": {"@type": "Answer", "text": "Not the way a relational-query ORM requires it. Kysely's Database interface only describes column shapes and types per table — foreign key relationships (like posts.author_id referencing users.id) are expressed in migrations via.references(), but querying across tables still means writing an explicit.innerJoin() or similar in your query, rather than declaring the relationship once and having Kysely traverse it automatically."}}], "url": "https://capawesome.io/blog/how-to-use-kysely-with-capacitor-and-sqlite/"}
```
