Relevant source files
The following files were used as context for generating this wiki page:
- drizzle-zod/src/schema.ts
- drizzle-orm/type-tests/mysql/tables.ts
- drizzle-orm/type-tests/pg/tables.ts
- drizzle-valibot/src/schema.ts
- drizzle-kit/src/snapshotsDiffer.ts
- drizzle-kit/src/serializer/pgSchema.ts
- drizzle-arktype/src/schema.ts
- drizzle-typebox/src/schema.ts
- drizzle-kit/src/serializer/gelSchema.ts
- drizzle-zod/src/schema.types.internal.ts
- drizzle-kit/src/introspect-pg.ts
- drizzle-kit/src/introspect-gel.ts
- drizzle-valibot/src/column.ts
- drizzle-kit/src/introspect-mysql.ts
- drizzle-zod/src/column.ts
- drizzle-zod/src/column.types.ts
- drizzle-typebox/src/schema.types.internal.ts
- drizzle-kit/src/introspect-singlestore.ts
- drizzle-kit/src/serializer/sqliteSchema.ts
- drizzle-valibot/src/schema.types.internal.ts
- drizzle-valibot/src/column.types.ts
- drizzle-kit/src/serializer/mysqlSchema.ts
- drizzle-arktype/src/schema.types.internal.ts
- drizzle-typebox/src/column.ts
- drizzle-zod/src/schema.types.ts
- drizzle-kit/src/serializer/studio.ts
- drizzle-valibot/src/schema.types.ts
- drizzle-typebox/src/column.types.ts
- drizzle-typebox/src/schema.types.ts
- drizzle-arktype/src/schema.types.ts
Overview
Drizzle Zod Integration automatically generates modular Zod validation schemas directly from Drizzle ORM table and view definitions, bridging database schema declarations with runtime input validation. By analyzing column types, nullability constraints, default values, and generated identities, the integration produces precise Zod objects tailored for select, insert, and update operations. Custom schema factories allow developers to configure type coercion rules and custom Zod instances across operations. Sources: drizzle-zod/src/schema.ts:15-123, drizzle-zod/src/schema.types.ts:6-60
Schema Generation Architecture
Operation Condition Matrix
Public generator functions createSelectSchema, createInsertSchema, and createUpdateSchema analyze Drizzle ORM tables, views, and PostgreSQL enums to dynamically construct corresponding Zod validation schemas. The architecture relies on internal condition sets (selectConditions, insertConditions, and updateConditions) that dictate whether individual columns are omitted (never), marked optional (optional), or flagged as nullable (nullable) depending on the database operation.
Sources: drizzle-zod/src/schema.ts:76-120
| Operation Rule Set | Omitted Columns (never) | Optional Columns (optional) | Nullable Columns (nullable) |
|---|---|---|---|
selectConditions | () => false | () => false | !column.notNull |
insertConditions | generated.type === 'always' || generatedIdentity.type === 'always' | !column.notNull || (column.notNull && column.hasDefault) | !column.notNull |
updateConditions | generated.type === 'always' || generatedIdentity.type === 'always' | () => true | !column.notNull |
Sources: drizzle-zod/src/schema.ts:76-92
Column and Entity Processing
The core schema builder resolves table and view columns via getColumns, which delegates to getTableColumns for table definitions and getViewSelectedFields for database views. Recursive execution handles nested relation structures and custom field groups.
function getColumns(tableLike: Table | View) {
return isTable(tableLike) ? getTableColumns(tableLike) : getViewSelectedFields(tableLike);
}
function handleColumns(
columns: Record<string, any>,
refinements: Record<string, any>,
conditions: Conditions,
factory?: CreateSchemaFactoryOptions<
Partial<Record<'bigint' | 'boolean' | 'date' | 'number' | 'string', true>> | true | undefined
>,
): z.ZodType {
const columnSchemas: Record<string, z.ZodType> = {};
for (const [key, selected] of Object.entries(columns)) {
if (!is(selected, Column) && !is(selected, SQL) && !is(selected, SQL.Aliased) && typeof selected === 'object') {
const columns = isTable(selected) || isView(selected) ? getColumns(selected) : selected;
columnSchemas[key] = handleColumns(columns, refinements[key] ?? {}, conditions, factory);
continue;
}Sources: drizzle-zod/src/schema.ts:15-34
Note
When processing PostgreSQL enums via createSelectSchema, the generator bypasses column inspection entirely, resolving directly to zod.enum(enum_.enumValues) or using a custom zodInstance supplied through createSchemaFactory.
Sources: drizzle-zod/src/schema.ts:66-74, drizzle-zod/src/schema.ts:98-100
Schema Factory Configuration
The createSchemaFactory function accepts options containing a custom zodInstance or global coerce rules, returning scoped instances of createSelectSchema, createInsertSchema, and createUpdateSchema bound to those configurations.
export function createSchemaFactory<
TCoerce extends Partial<Record<'bigint' | 'boolean' | 'date' | 'number' | 'string', true>> | true | undefined,
>(options?: CreateSchemaFactoryOptions<TCoerce>) {
const createSelectSchema: CreateSelectSchema<TCoerce> = (
entity: Table | View | PgEnum<[string, ...string[]]>,
refine?: Record<string, any>,
) => {
if (isPgEnum(entity)) {
return handleEnum(entity, options);
}
const columns = getColumns(entity);
return handleColumns(columns, refine ?? {}, selectConditions, options) as any;
};
const createInsertSchema: CreateInsertSchema<TCoerce> = (
entity: Table,
refine?: Record<string, any>,
) => {
const columns = getColumns(entity);
return handleColumns(columns, refine ?? {}, insertConditions, options) as any;
};
const createUpdateSchema: CreateUpdateSchema<TCoerce> = (
entity: Table,
refine?: Record<string, any>,
) => {
const columns = getColumns(entity);
return handleColumns(columns, refine ?? {}, updateConditions, options) as any;
};
return { createSelectSchema, createInsertSchema, createUpdateSchema };
}Sources: drizzle-zod/src/schema.ts:121-152
Warning
Columns defined with generatedalways or generatedIdentity set to 'always' evaluate to true under conditions.never for both insert and update operations, causing them to be entirely stripped from the resulting Zod object schemas.
Sources: drizzle-zod/src/schema.ts:83-83, drizzle-zod/src/schema.ts:89-89
Column Conversion and Dialect Mapping
Low-Level Column Mapping
The columnToSchema function inspects individual Drizzle column definitions to produce corresponding Zod type instances. For columns with defined enumerations, it constructs an enum schema using z.enum(column.enumValues), falling back to a standard string schema if the list of values is empty.
export function columnToSchema(
column: Column,
factory:
| CreateSchemaFactoryOptions<
Partial<Record<'bigint' | 'boolean' | 'date' | 'number' | 'string', true>> | true | undefined
>
| undefined,
): zod.ZodType {
const z: typeof zod = factory?.zodInstance ?? zod;
const coerce = factory?.coerce ?? {};
let schema!: zod.ZodType;
if (isWithEnum(column)) {
schema = column.enumValues.length ? z.enum(column.enumValues) : z.string();
}
Sources: drizzle-zod/src/column.ts:70-85
PostgreSQL geometric, vector, and line types receive specialized structural mapping. Tuple-based representations map to fixed-length Zod tuples, whereas object-based representations map to explicit Zod records with named coordinate keys.
if (!schema) {
// Handle specific types
if (isColumnType<PgGeometry<any> | PgPointTuple<any>>(column, ['PgGeometry', 'PgPointTuple'])) {
schema = z.tuple([z.number(), z.number()]);
} else if (
isColumnType<PgPointObject<any> | PgGeometryObject<any>>(column, ['PgGeometryObject', 'PgPointObject'])
) {
schema = z.object({ x: z.number(), y: z.number() });
} else if (isColumnType<PgHalfVector<any> | PgVector<any>>(column, ['PgHalfVector', 'PgVector'])) {
schema = z.array(z.number());
schema = column.dimensions ? (schema as zod.ZodArray<any>).length(column.dimensions) : schema;
} else if (isColumnType<PgLineTuple<any>>(column, ['PgLine'])) {
schema = z.tuple([z.number(), z.number(), z.number()]);
} else if (isColumnType<PgLineABC<any>>(column, ['PgLineABC'])) {
schema = z.object({
a: z.number(),
b: z.number(),
c: z.number(),
});
} // Handle other typesSources: drizzle-zod/src/column.ts:86-105
Note
Vector and array columns inspect column.dimensions or column.size properties to apply exact length validation constraints via Zod array length modifiers.
Sources: drizzle-zod/src/column.ts:94-96, drizzle-zod/src/column.ts:106-108
DataType and Coercion Handling
For standard data types, column mapping resolves primitive categories (number, bigint, boolean, date, string, json, custom, and buffer) and applies optional coercion settings defined via createSchemaFactory.
} else if (column.dataType === 'array') {
schema = z.array(z.any());
} else if (column.dataType === 'number') {
schema = numberColumnToSchema(column, z, coerce);
} else if (column.dataType === 'bigint') {
schema = bigintColumnToSchema(column, z, coerce);
} else if (column.dataType === 'boolean') {
schema = coerce === true || coerce.boolean ? z.coerce.boolean() : z.boolean();
} else if (column.dataType === 'date') {
schema = coerce === true || coerce.date ? z.coerce.date() : z.date();
} else if (column.dataType === 'string') {
schema = stringColumnToSchema(column, z, coerce);
} else if (column.dataType === 'json') {
schema = jsonSchema;
} else if (column.dataType === 'custom') {
schema = z.any();
} else if (column.dataType === 'buffer') {
schema = bufferSchema;
}
}Sources: drizzle-zod/src/column.ts:109-128
| Column DataType | Default Zod Mapping | Coercion Rule (coerce.* or true) |
|---|---|---|
number | numberColumnToSchema | Handled internally by numeric range/integer logic |
bigint | bigintColumnToSchema | Handled internally by bigint parsing rules |
boolean | z.boolean() | z.coerce.boolean() |
date | z.date() | z.coerce.date() |
string | stringColumnToSchema | Handled internally by string configuration |
json | jsonSchema | Unaffected by scalar coercion |
custom | z.any() | Unaffected by scalar coercion |
buffer | bufferSchema | Unaffected by scalar coercion |
Sources: drizzle-zod/src/column.ts:111-127
Warning
If a column datatype matches none of the recognized branches and has no associated enum structure, columnToSchema defaults the resulting validation target to z.any().
Sources: drizzle-zod/src/column.ts:130-132
Enum Normalization and SQL Translation
PostgreSQL enums processed outside table definitions via handleEnum extract enum values directly from enum_.enumValues using the active Zod instance.
function handleEnum(
enum_: PgEnum<any>,
factory?: CreateSchemaFactoryOptions<
Partial<Record<'bigint' | 'boolean' | 'date' | 'number' | 'string', true>> | true | undefined
>,
) {
const zod: typeof z = factory?.zodInstance ?? z;
return zod.enum(enum_.enumValues);
}Sources: drizzle-zod/src/schema.ts:66-74
Numeric columns inspect their SQL type definitions through column.getSQLType() to detect unsigned flags, establishing strict min and max boundaries corresponding to database integer widths.
function numberColumnToSchema(
column: Column,
z: typeof zod,
coerce: CreateSchemaFactoryOptions<
Partial<Record<'bigint' | 'boolean' | 'date' | 'number' | 'string', true>> | true | undefined
>['coerce'],
): zod.ZodType {
let unsigned = column.getSQLType().includes('unsigned');
let min!: number;
let max!: number;
let integer = false;
if (isColumnType<MySqlTinyInt<any> | SingleStoreTinyInt<any>>(column, ['MySqlTinyInt', 'SingleStoreTinyInt'])) {
min = unsigned ? 0 : CONSTANTS.INT8_MIN;
max = unsigned ? CONSTANTS.INT8_UNSIGNED_MAX : CONSTANTS.INT8_MAX;
integer = true;Sources: drizzle-zod/src/column.ts:137-152
Type Inference and Internal Constraints
Compile-Time Schema Types and Utility Helpers
The compilation phase orchestrates how database tables, columns, and relations are mapped into strongly-typed Zod object schemas via recursive helper types. The core builder utility BuildSchema accepts an operation mode ('select' | 'insert' | 'update'), a record of columns, refinement configurations, and coercion rules. It iterates through table keys, applying generated-column filters where generated-always-as columns are restricted to select schemas while remaining present or omitted across other operation types.
export type BuildSchema<
TType extends 'select' | 'insert' | 'update',
TColumns extends Record<string, any>,
TRefinements extends Record<string, any> | undefined,
TCoerce extends Partial<Record<'bigint' | 'boolean' | 'date' | 'number' | 'string', true>> | true | undefined,
> = z.ZodObject<
Simplify<
{
[
K in keyof TColumns as ColumnIsGeneratedAlwaysAs<TColumns[K]> extends true ? TType extends 'select' ? K
: never
: K
]: TColumns[K] extends infer TColumn extends Column
? IsRefinementDefined<TRefinements, K> extends true
? Assume<HandleRefinement<TType, TRefinements[K & keyof TRefinements], TColumn>, z.ZodType>
: HandleColumn<TType, TColumn, TCoerce>
: TColumns[K] extends infer TObject extends SelectedFieldsFlat<Column> | Table | View ? BuildSchema<
TType,
GetSelection<TObject>,
TRefinements extends object ? TRefinements[K & keyof TRefinements] : undefined,
TCoerce
>
: z.ZodAny;
}
>,
{ out: {}; in: {} }
>;Sources: drizzle-zod/src/schema.types.internal.ts:40-66
The framework evaluates column nullability and default states through conditional wrapper types like HandleSelectColumn, HandleInsertColumn, and HandleUpdateColumn. Required select fields retain their underlying Zod schema types, whereas nullable columns wrap them in z.ZodNullable. Insert and update helpers inject z.ZodOptional based on whether columns possess default values or allow null assignment.
type HandleSelectColumn<
TSchema extends z.ZodType,
TColumn extends Column,
> = TColumn['_']['notNull'] extends true ? TSchema
: z.ZodNullable<TSchema>;
type HandleInsertColumn<
TSchema extends z.ZodType,
TColumn extends Column,
> = TColumn['_']['notNull'] extends true ? TColumn['_']['hasDefault'] extends true ? z.ZodOptional<TSchema>
: TSchema
: z.ZodOptional<z.ZodNullable<TSchema>>;
type HandleUpdateColumn<
TSchema extends z.ZodType,
TColumn extends Column,
> = TColumn['_']['notNull'] extends true ? z.ZodOptional<TSchema>
: z.ZodOptional<z.ZodNullable<TSchema>>;
Sources: drizzle-zod/src/column.types.ts:76-94
| Operation Mode | Nullable & No Default | Not-Null & Has Default | Not-Null & No Default |
|---|---|---|---|
select | z.ZodNullable<TSchema> | TSchema | TSchema |
insert | z.ZodOptional<z.ZodNullable<TSchema>> | z.ZodOptional<TSchema> | TSchema |
update | z.ZodOptional<z.ZodNullable<TSchema>> | z.ZodOptional<TSchema> | z.ZodOptional<TSchema> |
Sources: drizzle-zod/src/column.types.ts:76-94
Note
Selection fields supporting nested relations or embedded objects recursively invoke BuildSchema against sub-selections derived from GetSelection<TObject>.
Sources: drizzle-zod/src/schema.types.internal.ts:56-61
Guardrails and DrizzleTypeError Protection
To prevent invalid schema configurations at compile time, internal type assertions use DrizzleTypeError guardrails. When schema refinements introduce keys that do not exist on the target comparison type, NoUnknownKeys intercepts the invalid property and triggers a descriptive compile-time failure.
export type NoUnknownKeys<
TRefinement extends Record<string, any>,
TCompare extends Record<string, any>,
> = {
[K in keyof TRefinement]: K extends keyof TCompare
? TRefinement[K] extends Record<string, z.ZodType> ? NoUnknownKeys<TRefinement[K], TCompare[K]>
: TRefinement[K]
: DrizzleTypeError<`Found unknown key in refinement: "${K & string}"`>;
};Sources: drizzle-zod/src/schema.types.internal.ts:68-76
Type coercion validation relies on the CanCoerce helper, which checks whether global coercion is enabled (true) or if specific primitive flags within the partial coercion record are marked as true for types like bigint, boolean, date, number, or string.
type CanCoerce<
TCoerce extends Partial<Record<'bigint' | 'boolean' | 'date' | 'number' | 'string', true>> | true | undefined,
TTo extends 'bigint' | 'boolean' | 'date' | 'number' | 'string',
> = TCoerce extends true ? true
: TCoerce extends Record<string, any> ? TCoerce[TTo] extends true ? true
: false
: false;Sources: drizzle-zod/src/column.types.ts:36-42
Warning
Passing unregistered keys inside refinement objects causes NoUnknownKeys to evaluate the property against DrizzleTypeError, halting compilation with an explicit error message naming the offending key.
Sources: drizzle-zod/src/schema.types.internal.ts:68-76
Custom Overrides and Refinement Pipelines
Column Refinements and Direct Replacements
The schema generation pipeline accepts an optional refinement record as its second parameter, allowing developers to customize individual column validation rules or completely replace generated Zod types. During schema building in handleColumns, the generator checks whether a refinement is defined for a given column key. If the refinement value is provided and is not a function, it directly overrides the column schema. If the refinement is defined as a function, it is invoked with the automatically generated column schema as its argument, enabling chaining or custom validation transformations.
function handleColumns(
columns: Record<string, any>,
refinements: Record<string, any>,
conditions: Conditions,
factory?: CreateSchemaFactoryOptions<
Partial<Record<'bigint' | 'boolean' | 'date' | 'number' | 'string', true>> | true | undefined
>,
): z.ZodType {
const columnSchemas: Record<string, z.ZodType> = {};
for (const [key, selected] of Object.entries(columns)) {
if (!is(selected, Column) && !is(selected, SQL) && !is(selected, SQL.Aliased) && typeof selected === 'object') {
const columns = isTable(selected) || isView(selected) ? getColumns(selected) : selected;
columnSchemas[key] = handleColumns(columns, refinements[key] ?? {}, conditions, factory);
continue;
}
const refinement = refinements[key];
if (refinement !== undefined && typeof refinement !== 'function') {
columnSchemas[key] = refinement;
continue;
}
const column = is(selected, Column) ? selected : undefined;
const schema = column ? columnToSchema(column, factory) : z.any();
const refined = typeof refinement === 'function' ? refinement(schema) : schema;
Sources: drizzle-zod/src/schema.ts:19-45
const userInsertSchema = createInsertSchema(users, {
email: (schema) => schema.email(),
age: z.number().min(18),
});Sources: drizzle-zod/src/schema.ts:105-111
Note
Refinement functions receive the exact base schema generated by columnToSchema, preserving nullability and optional wrapping unless explicitly overridden inside the transform callback.
Sources: drizzle-zod/src/schema.ts:42-45
Nested Structures and View Field Selections
When processing tables, views, or relational selections, handleColumns inspects entry values recursively. If an entry key represents a nested object or view rather than a primitive column or SQL expression, the generator retrieves the underlying column map via getColumns—which evaluates getTableColumns for tables and getViewSelectedFields for database views—and processes the nested record recursively using the matching refinement subsection.
function getColumns(tableLike: Table | View) {
return isTable(tableLike) ? getTableColumns(tableLike) : getViewSelectedFields(tableLike);
}
function handleColumns(
columns: Record<string, any>,
refinements: Record<string, any>,
conditions: Conditions,
factory?: CreateSchemaFactoryOptions<
Partial<Record<'bigint' | 'boolean' | 'date' | 'number' | 'string', true>> | true | undefined
>,
): z.ZodType {
const columnSchemas: Record<string, z.ZodType> = {};
for (const [key, selected] of Object.entries(columns)) {
if (!is(selected, Column) && !is(selected, SQL) && !is(selected, SQL.Aliased) && typeof selected === 'object') {
const columns = isTable(selected) || isView(selected) ? getColumns(selected) : selected;
columnSchemas[key] = handleColumns(columns, refinements[key] ?? {}, conditions, factory);
continue;
}Sources: drizzle-zod/src/schema.ts:15-34
Type checking for these nested refinement structures is enforced at compile time through recursive conditional typing. The BuildRefine type utility maps over columns and nested relations, validating that any provided refinement matches either a valid Zod type or a transformation function accepting the corresponding column type schema.
export type BuildRefine<
TColumns extends Record<string, any>,
TCoerce extends Partial<Record<'bigint' | 'boolean' | 'date' | 'number' | 'string', true>> | true | undefined,
> = {
[K in keyof TColumns as TColumns[K] extends Column | SelectedFieldsFlat<Column> | Table | View ? K : never]?:
TColumns[K] extends Column ? BuildRefineField<GetZodType<TColumns[K], TCoerce>>
: BuildRefine<GetSelection<TColumns[K]>, TCoerce>;
};Sources: drizzle-zod/src/schema.types.internal.ts:14-21
Warning
Supplying an unknown key inside a nested refinement tree trips the NoUnknownKeys helper, halting type compilation with a DrizzleTypeError indicating the illegal property name.
Sources: drizzle-zod/src/schema.types.internal.ts:68-76
Form Handling and Runtime Validation
Input Mutation Parsing and Mutation Validation
Generated Zod schemas integrate into application form handling and API request validation pipelines by defining runtime parsers for database mutations. Using createInsertSchema and createUpdateSchema, applications validate incoming payloads before executing database operations. The condition rules built into insertConditions and updateConditions automatically omit database-generated columns—such as columns configured with always generation or identity defaults—while enforcing required fields, nullability, and optional defaults for client-supplied payloads.
const insertConditions: Conditions = {
never: (column) => column?.generated?.type === 'always' || column?.generatedIdentity?.type === 'always',
optional: (column) => !column.notNull || (column.notNull && column.hasDefault),
nullable: (column) => !column.notNull,
};
const updateConditions: Conditions = {
never: (column) => column?.generated?.type === 'always' || column?.generatedIdentity?.type === 'always',
optional: () => true,
nullable: (column) => !column.notNull,
};
Sources: drizzle-zod/src/schema.ts:82-93
const insertUserSchema = createInsertSchema(users);
const validatedData = insertUserSchema.parse(requestBody);Sources: drizzle-zod/src/schema.ts:105-111
Caution
Columns defined with generatedAlwaysAs or always identity are completely excluded from insert and update schemas via conditions.never, causing Zod to strip these fields if submitted in client mutation payloads.
Sources: drizzle-zod/src/schema.ts:83-89
Query Result and View Validation
For read operations and user interface rendering, createSelectSchema generates runtime validation contracts that reflect exact table structures and database views. Unlike insertion handlers, select schemas evaluate columns against selectConditions, preserving all table fields while mapping nullable database columns to Zod nullable wrappers without marking them optional. This ensures API responses and UI form initializers match the exact shape queried from the database.
const selectConditions: Conditions = {
never: () => false,
optional: () => false,
nullable: (column) => !column.notNull,
};Sources: drizzle-zod/src/schema.ts:76-80
const selectUserSchema = createSelectSchema(users);
const userProfile = selectUserSchema.parse(dbRecord);Sources: drizzle-zod/src/schema.ts:94-103
Note
When passed a database view or an enumerated type directly, createSelectSchema resolves view selected fields via getViewSelectedFields or builds a Zod enum parser from enumValues.
Sources: drizzle-zod/src/schema.ts:15-17, drizzle-zod/src/schema.ts:98-100
Related
On This Page
OverviewSchema Generation ArchitectureOperation Condition MatrixColumn and Entity ProcessingSchema Factory ConfigurationColumn Conversion and Dialect MappingLow-Level Column MappingDataType and Coercion HandlingEnum Normalization and SQL TranslationType Inference and Internal ConstraintsCompile-Time Schema Types and Utility HelpersGuardrails and DrizzleTypeError ProtectionCustom Overrides and Refinement PipelinesColumn Refinements and Direct ReplacementsNested Structures and View Field SelectionsForm Handling and Runtime ValidationInput Mutation Parsing and Mutation ValidationQuery Result and View ValidationRelated