Skip to Content
🚀 Getting StartedDefining Database Types

Last Updated: 3/20/2026


Defining Database Types

Kysely’s type safety depends on a TypeScript interface that describes your database schema. This Database interface maps table names to table schema interfaces, and Kysely uses it to validate every query you write.

The Database Interface

The Database interface is a simple object type where keys are table names and values are table schema interfaces:

interface Database { person: PersonTable pet: PetTable }

Each table interface describes the columns in that table. Important: Table interfaces should only be used in the Database type. Never use them as the result type of a query. Instead, use the Selectable, Insertable, and Updateable utility types (explained below).

Defining Table Interfaces

A table interface lists every column with its TypeScript type:

interface PersonTable { id: Generated<number> first_name: string last_name: string | null age: number created_at: ColumnType<Date, string | undefined, never> }

Column Types

Simple columns use plain TypeScript types:

first_name: string age: number

Nullable columns use union types with null:

last_name: string | null

Do not use optional properties (last_name?: string). Kysely determines optionality automatically based on the operation (select, insert, update). A nullable column is always present in select results (as string | null), but it’s optional in inserts and updates.

Generated Columns

The Generated<T> type marks columns that are auto-generated by the database (like auto-incrementing primary keys):

id: Generated<number>

This makes the column optional in inserts and updates, but always present (and non-optional) in selects. The Generated type is defined in src/util/column-type.ts as:

type Generated<S> = ColumnType<S, S | undefined, S>

It’s a shorthand for ColumnType<number, number | undefined, number>, meaning:

  • Select type: number (always present)
  • Insert type: number | undefined (optional)
  • Update type: number (optional, because all update columns are optional)

ColumnType for Different Operations

The ColumnType<SelectType, InsertType, UpdateType> utility lets you specify different types for select, insert, and update operations:

created_at: ColumnType<Date, string | undefined, never>

This means:

  • Selects return a Date object (the driver parses the database timestamp)
  • Inserts accept a string or undefined (you can provide an ISO string or let the database default)
  • Updates use never (the column cannot be updated)

The ColumnType type is defined in src/util/column-type.ts. It’s a branded type with three phantom properties:

type ColumnType<SelectType, InsertType = SelectType, UpdateType = SelectType> = { readonly __select__: SelectType readonly __insert__: InsertType readonly __update__: UpdateType }

These properties never exist at runtime — they’re purely for TypeScript’s type system.

GeneratedAlways Columns

For columns that are always generated by the database (like PostgreSQL’s GENERATED ALWAYS AS IDENTITY), use GeneratedAlways<T>:

id: GeneratedAlways<number>

This is equivalent to ColumnType<number, never, never> — the column is read-only and cannot be inserted or updated.

JSON Columns

The JSONColumnType<T> utility is a shorthand for JSON columns:

metadata: JSONColumnType<{ login_at: string ip: string | null plan: 'free' | 'premium' }>

This expands to ColumnType<T, string, string>, meaning:

  • Selects return the parsed JSON object (type T)
  • Inserts and updates accept a JSON string

The driver handles JSON parsing/stringification. Kysely just provides the types.

Extracting Row Types

Table interfaces describe the database schema, but you shouldn’t use them directly in your application code. Instead, use these utility types from src/util/column-type.ts:

Selectable<T>

Extracts the select type from a table interface:

type Person = Selectable<PersonTable> // { // id: number // first_name: string // last_name: string | null // age: number // created_at: Date // }

Use this type for query results.

Insertable<T>

Extracts the insert type from a table interface:

type NewPerson = Insertable<PersonTable> // { // id?: number // first_name: string // last_name?: string | null // age: number // created_at?: string // }

Use this type for insert payloads. Notice that id and created_at are optional (because they’re Generated or have undefined in their insert type), and last_name is optional because it’s nullable.

Updateable<T>

Extracts the update type from a table interface:

type PersonUpdate = Updateable<PersonTable> // { // id?: number // first_name?: string // last_name?: string | null // age?: number // // created_at is missing because its update type is `never` // }

All columns are optional in updates (you only update the fields you want to change). Columns with never as their update type are excluded entirely.

Complete Example

Here’s a complete database schema with two tables:

import { ColumnType, Generated, Insertable, Selectable, Updateable } from 'kysely' export interface Database { person: PersonTable pet: PetTable } export interface PersonTable { id: Generated<number> first_name: string last_name: string | null age: number created_at: ColumnType<Date, string | undefined, never> } export interface PetTable { id: Generated<number> name: string owner_id: number species: 'dog' | 'cat' } // Row types for application code export type Person = Selectable<PersonTable> export type NewPerson = Insertable<PersonTable> export type PersonUpdate = Updateable<PersonTable> export type Pet = Selectable<PetTable> export type NewPet = Insertable<PetTable> export type PetUpdate = Updateable<PetTable>

Keeping Types in Sync

Your TypeScript types must match your actual database schema. If they drift, you’ll get runtime errors even though TypeScript says everything is fine.

For production applications, use a code generator to automatically create your Database interface by introspecting your database or Prisma schema. Popular options include:

  • kysely-codegen — Introspects your database and generates types
  • prisma-kysely — Generates Kysely types from Prisma schemas

These tools ensure your types always match your schema. Manual type definitions work for small projects but become error-prone as schemas grow.

Runtime vs. Compile-Time Types

Kysely only deals with compile-time types. The runtime JavaScript types (what you actually get from the database) are determined by the underlying driver (pg, mysql2, etc.).

For example, if your database stores a TIMESTAMP column, the driver might return a Date object, a string, or a number depending on its configuration. Kysely doesn’t transform these values — it just provides TypeScript types that match what the driver returns.

If the driver returns a Date but your table interface says string, you’ll get a type error when you try to use the result. Always verify what your driver returns and adjust your table interface accordingly.

What’s Next

  • First Query: Write your first SELECT query with Kysely and see how TypeScript validates every column reference in real time.