← Back to blog

April 25, 2026 · 4 min read

Drizzle ORM Type Mapping: SQL to TypeScript Cheat Sheet

A practical cheat sheet for mapping SQL column types to Drizzle ORM column builders and the TypeScript types they infer — across PostgreSQL, MySQL, and SQLite.

drizzletypescriptsqlcheat-sheetormpostgresmysqlsqlite

Every Drizzle column lives in three places at once: the SQL the migration emits, the column builder you call in TypeScript, and the inferred type your InferSelectModel returns. Get any one wrong and you'll find out at runtime — usually when JSON serialization quietly turns a bigint into null, or decimal into a string nobody expected.

Here's the cheat sheet I wish I'd had when I started writing Drizzle schemas.

The rule that explains 90% of the surprises

import { pgTable, integer, bigint, decimal } from "drizzle-orm/pg-core";
import type { InferSelectModel } from "drizzle-orm";

export const orders = pgTable("orders", {
  id: integer("id").primaryKey(),
  total: decimal("total", { precision: 10, scale: 2 }).notNull(),
  externalId: bigint("external_id", { mode: "number" }).notNull(),
});

type Order = InferSelectModel<typeof orders>;
// {
//   id: number;
//   total: string;          // ← yes, string
//   externalId: number;     // ← because mode: "number"
// }

Two takeaways:

  1. decimal and numeric return strings — Drizzle won't silently truncate 10.50 to a JS float. Want a number? Parse it yourself or use real.
  2. bigint requires a mode. "number" is safe up to 2^53; "bigint" gives you native BigInt. Pick once.

PostgreSQL

import { pgTable, /* … */ } from "drizzle-orm/pg-core";
SQL typeDrizzle builderTypeScript type
INTEGERinteger("col")number
SERIALserial("col")number
BIGINTbigint("col", { mode: "number" })number | bigint
NUMERIC(p,s)numeric("col", { precision, scale })string
DECIMAL(p,s)decimal("col", { precision, scale })string
REALreal("col")number
BOOLEANboolean("col")boolean
TEXTtext("col")string
VARCHAR(n)varchar("col", { length })string
UUIDuuid("col").defaultRandom()string
DATEdate("col")string
TIMESTAMPtimestamp("col")Date
TIMESTAMPTZtimestamp("col", { withTimezone: true })Date
JSONBjsonb("col").$type<Shape>()Shape
INTEGER[]integer("col").array()number[]

Two traps to memorize: numeric/decimal return string, and date returns string by default — timestamp is the one that gives you a real Date.

MySQL

import { mysqlTable, /* … */ } from "drizzle-orm/mysql-core";
SQL typeDrizzle builderTypeScript type
INTint("col")number
BIGINTbigint("col", { mode: "number" })number | bigint
SERIALserial("col")number
DOUBLEdouble("col")number
DECIMAL(p,s)decimal("col", { precision, scale })string
TINYINT(1)boolean("col")boolean
TEXTtext("col")string
VARCHAR(n)varchar("col", { length })string
DATETIMEdatetime("col")Date
TIMESTAMPtimestamp("col")Date
JSONjson("col").$type<Shape>()Shape
ENUM(...)mysqlEnum("col", ["a", "b"])"a" | "b"

MySQL's quirk: there's no real boolean — boolean() is just TINYINT(1).

SQLite

SQLite has five storage classes, so Drizzle leans on mode to give you richer TypeScript types over the same underlying columns:

import { sqliteTable, integer, text } from "drizzle-orm/sqlite-core";

export const events = sqliteTable("events", {
  id: integer("id").primaryKey({ autoIncrement: true }),
  active: integer("active", { mode: "boolean" }).notNull(),
  occurredAt: integer("occurred_at", { mode: "timestamp" }).notNull(),
  payload: text("payload", { mode: "json" }).$type<{ kind: string }>().notNull(),
});
SQL storageDrizzle builderTypeScript type
INTEGERinteger("col")number
INTEGERinteger("col", { mode: "boolean" })boolean
INTEGERinteger("col", { mode: "timestamp" })Date
REALreal("col")number
TEXTtext("col")string
TEXTtext("col", { enum: ["a", "b"] })"a" | "b"
TEXTtext("col", { mode: "json" }).$type<Shape>()Shape
BLOBblob("col")Buffer

The mental model: pick the storage class first (integer / text / blob), then use mode to tell Drizzle how to (de)serialize.

Rules that apply everywhere

Look it up without leaving your editor

This post covers the types you'll hit 95% of the time, but the long tail is real — interval, tsvector, cidr, mediumtext, varbinary, SQLite's blob modes. Rather than scrolling Drizzle's docs every time, keep this open in a tab:

Drizzle ORM Type Reference

Tabs for Postgres / MySQL / SQLite, live search, click-to-copy on every Drizzle method. Bookmark it and stop guessing whether decimal returns a number.