Last Updated: 3/20/2026
Reusable Helpers
Building reusable type-safe query helpers is one of Kysely’s strengths. By returning Expression<T> from helper functions, you can compose complex queries from small, testable pieces while maintaining full type safety.
The Expression Pattern
An Expression<T> (defined in src/expression/expression.ts) is anything that can be converted to an operation node and has a type. Helper functions that return Expression<T> can be used anywhere an expression is expected: in SELECT lists, WHERE clauses, HAVING clauses, ORDER BY, and more.
The key is to use expressionBuilder() (from src/expression/expression-builder.ts) to create expressions without assuming a specific query context:
import { expressionBuilder, type Expression } from 'kysely'
function fullName<DB>(): Expression<string> {
const eb = expressionBuilder<DB, 'person'>()
return eb.fn<string>('concat', [
eb.ref('first_name'),
eb.val(' '),
eb.ref('last_name')
])
}This helper can be used in any query that has a person table in scope:
const result = await db
.selectFrom('person')
.select([
'id',
fullName<Database>().as('full_name')
])
.execute()The generated SQL (PostgreSQL):
select "id", concat("first_name", $1, "last_name") as "full_name"
from "person"Using eb.ref() and eb.val()
Use eb.ref() to reference columns and eb.val() to pass values. This ensures type safety and proper parameter binding:
import { expressionBuilder, type Expression } from 'kysely'
function isAdult<DB>(): Expression<boolean> {
const eb = expressionBuilder<DB, 'person'>()
return eb('age', '>=', 18)
}
const adults = await db
.selectFrom('person')
.where(isAdult<Database>())
.selectAll()
.execute()The generated SQL (PostgreSQL):
select * from "person" where "age" >= $1Parameterized Helpers
Helpers can accept parameters to make them more flexible:
import { expressionBuilder, type Expression } from 'kysely'
function ageGreaterThan<DB>(minAge: number): Expression<boolean> {
const eb = expressionBuilder<DB, 'person'>()
return eb('age', '>', minAge)
}
const result = await db
.selectFrom('person')
.where(ageGreaterThan<Database>(21))
.selectAll()
.execute()The generated SQL (PostgreSQL):
select * from "person" where "age" > $1Combining Helpers
Helpers can be composed using eb.and(), eb.or(), and other expression builder methods:
import { expressionBuilder, type Expression } from 'kysely'
function isAdult<DB>(): Expression<boolean> {
const eb = expressionBuilder<DB, 'person'>()
return eb('age', '>=', 18)
}
function isSenior<DB>(): Expression<boolean> {
const eb = expressionBuilder<DB, 'person'>()
return eb('age', '>=', 65)
}
function isWorkingAge<DB>(): Expression<boolean> {
const eb = expressionBuilder<DB, 'person'>()
return eb.and([
isAdult<DB>(),
eb.not(isSenior<DB>())
])
}
const result = await db
.selectFrom('person')
.where(isWorkingAge<Database>())
.selectAll()
.execute()The generated SQL (PostgreSQL):
select * from "person" where ("age" >= $1 and not ("age" >= $2))Helpers with Subqueries
Helpers can include subqueries using eb.selectFrom():
import { expressionBuilder, type Expression } from 'kysely'
function hasPets<DB>(): Expression<boolean> {
const eb = expressionBuilder<DB, 'person'>()
return eb.exists(
eb.selectFrom('pet')
.whereRef('pet.owner_id', '=', 'person.id')
.select('pet.id')
)
}
const result = await db
.selectFrom('person')
.where(hasPets<Database>())
.selectAll()
.execute()The generated SQL (PostgreSQL):
select * from "person"
where exists (
select "pet"."id" from "pet" where "pet"."owner_id" = "person"."id"
)Helpers for Aggregations
Create helpers for common aggregations:
import { expressionBuilder, type Expression } from 'kysely'
function petCount<DB>(): Expression<number> {
const eb = expressionBuilder<DB, 'person'>()
return eb.selectFrom('pet')
.whereRef('pet.owner_id', '=', 'person.id')
.select((eb) => eb.fn.count('pet.id').as('count'))
}
const result = await db
.selectFrom('person')
.select([
'id',
'first_name',
petCount<Database>().as('pet_count')
])
.execute()The generated SQL (PostgreSQL):
select
"id",
"first_name",
(
select count("pet"."id") as "count"
from "pet"
where "pet"."owner_id" = "person"."id"
) as "pet_count"
from "person"Avoiding Context Assumptions
Don’t assume a specific query context in your helpers. Use generic type parameters to make them work with any database schema:
// ❌ Bad: Assumes a specific Database type
function isAdult(): Expression<boolean> {
const eb = expressionBuilder<MyDatabase, 'person'>()
return eb('age', '>=', 18)
}
// ✅ Good: Generic over the database type
function isAdult<DB>(): Expression<boolean> {
const eb = expressionBuilder<DB, 'person'>()
return eb('age', '>=', 18)
}This makes the helper reusable across different database schemas and testable in isolation.
Helpers with Raw SQL
Combine helpers with raw SQL when needed:
import { sql, expressionBuilder, type Expression } from 'kysely'
function searchName<DB>(query: string): Expression<boolean> {
const eb = expressionBuilder<DB, 'person'>()
return sql<boolean>`
to_tsvector('english', ${eb.ref('first_name')} || ' ' || ${eb.ref('last_name')})
@@ plainto_tsquery('english', ${query})
`
}
const result = await db
.selectFrom('person')
.where(searchName<Database>('John'))
.selectAll()
.execute()The generated SQL (PostgreSQL):
select * from "person"
where to_tsvector('english', "first_name" || ' ' || "last_name")
@@ plainto_tsquery('english', $1)Testing Helpers
Because helpers return Expression<T> and don’t execute queries, they’re easy to test:
import { expressionBuilder } from 'kysely'
describe('isAdult', () => {
it('creates the correct expression', () => {
const expr = isAdult<Database>()
const eb = expressionBuilder<Database, 'person'>()
// You can compile the expression to SQL for testing
const compiled = eb(expr).compile()
expect(compiled.sql).toContain('"age" >= $1')
expect(compiled.parameters).toEqual([18])
})
})Organizing Helpers
Group related helpers in modules:
// helpers/person.ts
import { expressionBuilder, type Expression } from 'kysely'
export function isAdult<DB>(): Expression<boolean> {
const eb = expressionBuilder<DB, 'person'>()
return eb('age', '>=', 18)
}
export function fullName<DB>(): Expression<string> {
const eb = expressionBuilder<DB, 'person'>()
return eb.fn<string>('concat', [
eb.ref('first_name'),
eb.val(' '),
eb.ref('last_name')
])
}
// Use in queries
import * as personHelpers from './helpers/person'
const result = await db
.selectFrom('person')
.where(personHelpers.isAdult<Database>())
.select([
'id',
personHelpers.fullName<Database>().as('full_name')
])
.execute()What’s Next
- Expressions — Learn more about the expression builder API.
- Raw SQL — Combine helpers with raw SQL for maximum flexibility.
- Plugins — Transform helper expressions using plugins.