Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/kysely-org/kysely/llms.txt

Use this file to discover all available pages before exploring further.

Kysely uses a fluent builder pattern that allows you to construct SQL queries through method chaining. Each method returns a new query builder instance, making queries immutable and composable.

The Builder Pattern

Every query in Kysely starts from the Kysely instance and builds up through method chaining:
import { Kysely, SqliteDialect } from 'kysely'
import Database from 'better-sqlite3'

interface DB {
  person: {
    id: Generated<number>
    first_name: string
    last_name: string | null
    age: number
  }
}

const db = new Kysely<DB>({
  dialect: new SqliteDialect({
    database: new Database(':memory:'),
  })
})

// Simple SELECT query
const people = await db
  .selectFrom('person')
  .select(['id', 'first_name', 'last_name'])
  .execute()

Immutability and Composability

Each method returns a new query builder instance, leaving the original unchanged:
const baseQuery = db
  .selectFrom('person')
  .select(['id', 'first_name'])

// These don't modify baseQuery
const adults = baseQuery.where('age', '>=', 18)
const seniors = baseQuery.where('age', '>=', 65)

// baseQuery is still unchanged
const everyone = await baseQuery.execute()
This immutability makes queries composable and reusable:
function applyPagination<DB, TB extends keyof DB, O>(
  query: SelectQueryBuilder<DB, TB, O>,
  page: number,
  pageSize: number
) {
  return query
    .limit(pageSize)
    .offset(page * pageSize)
}

const paginatedQuery = applyPagination(
  db.selectFrom('person').selectAll(),
  0,
  10
)

SELECT Queries

Basic Selection

// Select specific columns
const result = await db
  .selectFrom('person')
  .select(['first_name', 'last_name'])
  .execute()

// Select all columns
const all = await db
  .selectFrom('person')
  .selectAll()
  .execute()

// Select with alias
const aliased = await db
  .selectFrom('person')
  .select(['first_name as firstName', 'last_name as lastName'])
  .execute()

WHERE Clauses

// Simple comparison
await db
  .selectFrom('person')
  .selectAll()
  .where('age', '>', 18)
  .execute()

// Multiple conditions (AND)
await db
  .selectFrom('person')
  .selectAll()
  .where('age', '>', 18)
  .where('first_name', '=', 'Jennifer')
  .execute()

// OR conditions
await db
  .selectFrom('person')
  .selectAll()
  .where((eb) => eb.or([
    eb('age', '<', 18),
    eb('age', '>', 65)
  ]))
  .execute()

JOINs

interface DB {
  person: { id: Generated<number>; first_name: string }
  pet: { id: Generated<number>; name: string; owner_id: number }
}

// Inner join
const result = await db
  .selectFrom('person')
  .innerJoin('pet', 'pet.owner_id', 'person.id')
  .select([
    'person.id',
    'person.first_name',
    'pet.name as pet_name'
  ])
  .execute()

// Left join
await db
  .selectFrom('person')
  .leftJoin('pet', 'pet.owner_id', 'person.id')
  .select(['person.first_name', 'pet.name'])
  .execute()

// Join with callback for complex conditions
await db
  .selectFrom('person')
  .innerJoin('pet', (join) => join
    .onRef('pet.owner_id', '=', 'person.id')
    .on('pet.species', '=', 'dog')
  )
  .selectAll()
  .execute()

Aggregation and Grouping

const { count } = db.fn

const result = await db
  .selectFrom('person')
  .innerJoin('pet', 'pet.owner_id', 'person.id')
  .select([
    'person.id',
    count('pet.id').as('pet_count')
  ])
  .groupBy('person.id')
  .having((eb) => eb(count('pet.id'), '>', 1))
  .execute()

INSERT Queries

// Single insert
const result = await db
  .insertInto('person')
  .values({
    first_name: 'Jennifer',
    last_name: 'Aniston',
    age: 40
  })
  .execute()

// Multiple inserts
await db
  .insertInto('person')
  .values([
    { first_name: 'Jennifer', last_name: 'Aniston', age: 40 },
    { first_name: 'Arnold', last_name: 'Schwarzenegger', age: 70 }
  ])
  .execute()

// Insert with returning (PostgreSQL, SQLite)
const inserted = await db
  .insertInto('person')
  .values({ first_name: 'Jennifer', last_name: 'Aniston', age: 40 })
  .returningAll()
  .executeTakeFirst()

// inserted.id, inserted.first_name, etc. are available

UPDATE Queries

// Update with WHERE
await db
  .updateTable('person')
  .set({ age: 41 })
  .where('first_name', '=', 'Jennifer')
  .execute()

// Update with expression
await db
  .updateTable('person')
  .set((eb) => ({
    age: eb('age', '+', 1)
  }))
  .where('id', '=', 1)
  .execute()

// Update with returning
const updated = await db
  .updateTable('person')
  .set({ age: 41 })
  .where('id', '=', 1)
  .returningAll()
  .executeTakeFirst()

DELETE Queries

// Delete with WHERE
await db
  .deleteFrom('person')
  .where('age', '<', 18)
  .execute()

// Delete with multiple conditions
await db
  .deleteFrom('person')
  .where('first_name', '=', 'Jennifer')
  .where('last_name', '=', 'Aniston')
  .execute()

// Delete with returning
const deleted = await db
  .deleteFrom('person')
  .where('id', '=', 1)
  .returningAll()
  .executeTakeFirst()

Execution Methods

Kysely provides several methods to execute queries:
// execute() - returns all results
const all = await db.selectFrom('person').selectAll().execute()
// QueryResult<Person[]>

// executeTakeFirst() - returns first result or undefined
const first = await db.selectFrom('person').selectAll().executeTakeFirst()
// Person | undefined

// executeTakeFirstOrThrow() - returns first result or throws
const firstOrThrow = await db.selectFrom('person')
  .selectAll()
  .executeTakeFirstOrThrow()
// Person (throws NoResultError if not found)

Dynamic Queries

Build queries conditionally:
let query = db.selectFrom('person').selectAll()

if (minAge) {
  query = query.where('age', '>=', minAge)
}

if (firstName) {
  query = query.where('first_name', '=', firstName)
}

const results = await query.execute()
Or use helper functions:
function applyFilters<DB, TB extends keyof DB, O>(
  query: SelectQueryBuilder<DB, TB, O>,
  filters: { minAge?: number; firstName?: string }
) {
  let result = query
  
  if (filters.minAge !== undefined) {
    result = result.where('age', '>=', filters.minAge)
  }
  
  if (filters.firstName) {
    result = result.where('first_name', '=', filters.firstName)
  }
  
  return result
}

Method Chaining Reference

Common method chains for each query type: SELECT:
db.selectFrom() → select() → where() → orderBy() → limit() → execute()
INSERT:
db.insertInto() → values() → returning() → execute()
UPDATE:
db.updateTable() → set() → where() → returning() → execute()
DELETE:
db.deleteFrom() → where() → returning() → execute()
Every query builder method returns a new instance, so you can safely reuse base queries.
The type parameter changes as you chain methods, ensuring you can only reference columns and tables that are in scope.