Skip to main content
doc0blade47/drizzle-ormdocumentation home
Sign in

Drizzle ArkType Integration

Last updated Oct 12, 202693% grounded
Relevant source files

The following files were used as context for generating this wiki page:

Overview

Drizzle ArkType Integration bridges Drizzle ORM definitions with the ArkType validation library by automatically generating runtime type validators and static TypeScript types from database schemas. Operating across tables, views, and enums, it translates column primitives, constraints, and relational selections into validated ArkType structures, powering type-safe data validation and insertions.

Sources: drizzle-arktype/src/column.ts:66-126, drizzle-arktype/src/schema.ts:61-98, drizzle-arktype/src/schema.types.ts:6-48

Public Schema Generation API

Schema Factory Functions

The public API provides three main factory functions — createSelectSchema, createInsertSchema, and createUpdateSchema — to generate ArkType validation schemas from Drizzle table, view, and enum definitions. Each factory inspects the entity columns or fields, applies custom field refinements, and enforces nullability and optionality rules according to the operation type.

export const createSelectSchema = ((
	entity: Table | View | PgEnum<[string, ...string[]]>,
	refine?: Record<string, any>,
) => {
	if (isPgEnum(entity)) {
		return type.enumerated(...entity.enumValues);
	}
	const columns = getColumns(entity);
	return handleColumns(columns, refine ?? {}, {
		never: () => false,
		optional: () => false,
		nullable: (column) => !column.notNull,
	}) as any;
}) as CreateSelectSchema;
 
export const createInsertSchema = ((
	entity: Table,
	refine?: Record<string, any>,
) => {
	const columns = getColumns(entity);
	return handleColumns(columns, refine ?? {}, {
		never: (column) => column?.generated?.type === 'always' || column?.generatedIdentity?.type === 'always',
		optional: (column) => !column.notNull || (column.notNull && column.hasDefault),
		nullable: (column) => !column.notNull,
	}) as any;
}) as CreateInsertSchema;
 
export const createUpdateSchema = ((
	entity: Table,
	refine?: Record<string, any>,
) => {
	const columns = getColumns(entity);
	return handleColumns(columns, refine ?? {}, {
		never: (column) => column?.generated?.type === 'always' || column?.generatedIdentity?.type === 'always',
		optional: () => true,
		nullable: (column) => !column.notNull,
	}) as any;
}) as CreateUpdateSchema;

Sources: drizzle-arktype/src/schema.ts:61-98

The behavior of each factory function depends on specific condition evaluators configured during column iteration:

FunctionTarget Entitynever Conditionoptional Conditionnullable Condition
createSelectSchemaTable, View, PgEnumAlways falseAlways false!column.notNull
createInsertSchemaTablegenerated === 'always' or generatedIdentity === 'always'`!column.notNull
createUpdateSchemaTablegenerated === 'always' or generatedIdentity === 'always'Always true!column.notNull
export const createSelectSchema = ((
	entity: Table | View | PgEnum<[string, ...string[]]>,
	refine?: Record<string, any>,
) => {
	if (isPgEnum(entity)) {
		return type.enumerated(...entity.enumValues);
	}
	const columns = getColumns(entity);
	return handleColumns(columns, refine ?? {}, {
		never: () => false,
		optional: () => false,
		nullable: (column) => !column.notNull,
	}) as any;
}) as CreateSelectSchema;
 
export const createInsertSchema = ((
	entity: Table,
	refine?: Record<string, any>,
) => {
	const columns = getColumns(entity);
	return handleColumns(columns, refine ?? {}, {
		never: (column) => column?.generated?.type === 'always' || column?.generatedIdentity?.type === 'always',
		optional: (column) => !column.notNull || (column.notNull && column.hasDefault),
		nullable: (column) => !column.notNull,
	}) as any;
}) as CreateInsertSchema;
 
export const createUpdateSchema = ((
	entity: Table,
	refine?: Record<string, any>,
) => {
	const columns = getColumns(entity);
	return handleColumns(columns, refine ?? {}, {
		never: (column) => column?.generated?.type === 'always' || column?.generatedIdentity?.type === 'always',
		optional: () => true,
		nullable: (column) => !column.notNull,
	}) as any;
}) as CreateUpdateSchema;

Sources: drizzle-arktype/src/schema.ts:61-98

Note

When createSelectSchema receives a PgEnum, it bypasses column iteration entirely and immediately constructs an ArkType enumerated validator using the enum's enumValues.

Sources: drizzle-arktype/src/schema.ts:65-67

Overloads and Refinement Signatures

The TypeScript interfaces for createSelectSchema, createInsertSchema, and createUpdateSchema support overloaded signatures for tables and views, as well as optional refinement objects to customize field validators.

export interface CreateSelectSchema {
	<TTable extends Table>(table: TTable): BuildSchema<'select', TTable['_']['columns'], undefined>;
	<
		TTable extends Table,
		TRefine extends BuildRefine<TTable['_']['columns']>,
	>(
		table: TTable,
		refine?: NoUnknownKeys<TRefine, TTable['$inferSelect']>,
	): BuildSchema<'select', TTable['_']['columns'], TRefine>;
 
	<TView extends View>(view: TView): BuildSchema<'select', TView['_']['selectedFields'], undefined>;
	<
		TView extends View,
		TRefine extends BuildRefine<TView['_']['selectedFields']>,
	>(
		view: TView,
		refine: NoUnknownKeys<TRefine, TView['$inferSelect']>,
	): BuildSchema<'select', TView['_']['selectedFields'], TRefine>;
 
	<TEnum extends PgEnum<any>>(enum_: TEnum): Type<TEnum['enumValues'][number]>;
}
 
export interface CreateInsertSchema {
	<TTable extends Table>(table: TTable): BuildSchema<'insert', TTable['_']['columns'], undefined>;
	<
		TTable extends Table,
		TRefine extends BuildRefine<Pick<TTable['_']['columns'], keyof TTable['$inferInsert']>>,
	>(
		table: TTable,
		refine?: NoUnknownKeys<TRefine, TTable['$inferInsert']>,
	): BuildSchema<'insert', TTable['_']['columns'], TRefine>;
}
 
export interface CreateUpdateSchema {
	<TTable extends Table>(table: TTable): BuildSchema<'update', TTable['_']['columns'], undefined>;
	<
		TTable extends Table,
		TRefine extends BuildRefine<Pick<TTable['_']['columns'], keyof TTable['$inferInsert']>>,
	>(
		table: TTable,
		refine?: TRefine,
	): BuildSchema<'update', TTable['_']['columns'], TRefine>;
}

Sources: drizzle-arktype/src/schema.types.ts:6-48

Important

For insert and update schemas, refinement typings are constrained via NoUnknownKeys to keys present in $inferInsert, preventing developers from specifying refinements for uninsertable or generated columns.

Sources: drizzle-arktype/src/schema.types.ts:28-48

Column to ArkType Conversion Pipeline

Column Data Type Routing

The columnToSchema function inspects incoming Drizzle column definitions and routes them to specific ArkType validators. If a column has associated enumeration values, it generates an enumerated validator using column.enumValues, or falls back to type.string if the enumeration is empty.

export function columnToSchema(column: Column): Type {
	let schema!: Type;
 
	if (isWithEnum(column)) {
		schema = column.enumValues.length ? type.enumerated(...column.enumValues) : type.string;
	}
 

Sources: drizzle-arktype/src/column.ts:66-72

For specialized geometric, vector, and line types, columns map directly to fixed-tuple structures or object shapes with coordinate properties. Array columns recursively resolve their base column schema via columnToSchema and enforce exact element dimensions when a column size or dimension is specified.

Column Type MatcherColumn Subtypes CheckedGenerated ArkType Structure
Tuple Geometry / PointPgGeometry, PgPointTupletype([type.number, type.number])
Object Geometry / PointPgGeometryObject, PgPointObjecttype({ x: type.number, y: type.number })
VectorPgHalfVector, PgVectortype.number.array() (with optional .exactlyLength(column.dimensions))
Line TuplePgLinetype([type.number, type.number, type.number])
Line ABC ObjectPgLineABCtype({ a: type.number, b: type.number, c: type.number })
ArrayPgArraycolumnToSchema(column.baseColumn).array() (with optional .exactlyLength(column.size))
// Handle specific types
if (isColumnType<PgGeometry<any> | PgPointTuple<any>>(column, ['PgGeometry', 'PgPointTuple'])) {
	schema = type([type.number, type.number]);
} else if (
	isColumnType<PgPointObject<any> | PgGeometryObject<any>>(column, ['PgGeometryObject', 'PgPointObject'])
) {
	schema = type({
		x: type.number,
		y: type.number,
	});
} else if (isColumnType<PgHalfVector<any> | PgVector<any>>(column, ['PgHalfVector', 'PgVector'])) {
	schema = column.dimensions
		? type.number.array().exactlyLength(column.dimensions)
		: type.number.array();
} else if (isColumnType<PgLineTuple<any>>(column, ['PgLine'])) {
	schema = type([type.number, type.number, type.number]);
} else if (isColumnType<PgLineABC<any>>(column, ['PgLineABC'])) {
	schema = type({
		a: type.number,
		b: type.number,
		c: type.number,
	});
} // Handle other types
else if (isColumnType<PgArray<any, any>>(column, ['PgArray'])) {
	const arraySchema = columnToSchema(column.baseColumn).array();
	schema = column.size ? arraySchema.exactlyLength(column.size) : arraySchema;
} else if (column.dataType === 'array') {
	schema = type.unknown.array();

Sources: drizzle-arktype/src/column.ts:74-101

Primitive and Complex Type Mappings

Standard data types are resolved by checking column.dataType. Numeric and bigint columns delegate to dedicated boundary-resolution functions, while booleans map to type.boolean, dates map to type.Date, JSON columns map to jsonSchema, buffers map to bufferSchema, and custom types fall back to type.unknown.

} else if (column.dataType === 'number') {
	schema = numberColumnToSchema(column);
} else if (column.dataType === 'bigint') {
	schema = bigintColumnToSchema(column);
} else if (column.dataType === 'boolean') {
	schema = type.boolean;
} else if (column.dataType === 'date') {
	schema = type.Date;
} else if (column.dataType === 'string') {
	schema = stringColumnToSchema(column);
} else if (column.dataType === 'json') {
	schema = jsonSchema;
} else if (column.dataType === 'custom') {
	schema = type.unknown;
} else if (column.dataType === 'buffer') {
	schema = bufferSchema;
}

Sources: drizzle-arktype/src/column.ts:102-118

Note

If a column matches none of the recognized database types or enumerations, columnToSchema defaults to type.unknown as a safe fallback validator.

Sources: drizzle-arktype/src/column.ts:121-125

Column Nullability and Operational Handling

The HandleColumn type utility determines whether a generated schema requires nullability or optionality wrappers based on whether an operation targets select, insert, or update statements. Nullable columns wrap their underlying schemas with ArktypeNullable, allowing null values, whereas defaultable insert columns and all update columns incorporate ArktypeOptional.

export type HandleColumn<
	TType extends 'select' | 'insert' | 'update',
	TColumn extends Column,
> = TType extends 'select' ? HandleSelectColumn<GetArktypeType<TColumn>, TColumn>
	: TType extends 'insert' ? HandleInsertColumn<GetArktypeType<TColumn>, TColumn>
	: TType extends 'update' ? HandleUpdateColumn<GetArktypeType<TColumn>, TColumn>
	: GetArktypeType<TColumn>;

Sources: drizzle-arktype/src/column.types.ts:35-41

Important

JSON columns use specialized type extraction via GetArktypeType, which resolves unconstrained JSON data columns to the Json union type rather than standard unknown descriptors.

Sources: drizzle-arktype/src/column.types.ts:9-14

Table and View Column Resolution

Column Extraction and Recursive Traversal

The getColumns helper inspects whether an incoming entity is a table or view, invoking getTableColumns or getViewSelectedFields respectively to extract its base field mappings.

function getColumns(tableLike: Table | View) {
	return isTable(tableLike) ? getTableColumns(tableLike) : getViewSelectedFields(tableLike);
}

Sources: drizzle-arktype/src/schema.ts:10-12

The handleColumns engine iterates over column records and recursively processes nested object relations or inline selections whenever an entry is an object and not an instance of Column, SQL, or SQL.Aliased.

function handleColumns(
	columns: Record<string, any>,
	refinements: Record<string, any>,
	conditions: Conditions,
): Type {
	const columnSchemas: Record<string, Type> = {};
 
	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);
			continue;
		}

Sources: drizzle-arktype/src/schema.ts:14-26

Conditional Constraints and Refinement Processing

During column iteration, handleColumns applies operational conditions and field-level user refinements to build the final composite Type schema object.

 
		const refinement = refinements[key];
		if (
			refinement !== undefined
			&& (typeof refinement !== 'function' || (typeof refinement === 'function' && refinement.expression !== undefined))
		) {
			columnSchemas[key] = refinement;
			continue;
		}
 
		const column = is(selected, Column) ? selected : undefined;
		const schema = column ? columnToSchema(column) : type.unknown;
		const refined = typeof refinement === 'function' ? refinement(schema) : schema;
 
		if (conditions.never(column)) {
			continue;
		} else {
			columnSchemas[key] = refined;
		}
 
		if (column) {
			if (conditions.nullable(column)) {
				columnSchemas[key] = columnSchemas[key]!.or(type.null);
			}
 
			if (conditions.optional(column)) {
				columnSchemas[key] = columnSchemas[key]!.optional() as any;
			}
		}
	}
 
	return type(columnSchemas);
}

Sources: drizzle-arktype/src/schema.ts:27-59

Note

When conditions.never(column) evaluates to true—such as for database-generated columns in insert or update schemas—the property is omitted from the resulting ArkType definition entirely.

Sources: drizzle-arktype/src/schema.ts:41-45

Important

The createSelectSchema, createInsertSchema, and createUpdateSchema factories supply distinct condition rulesets governing nullability (!column.notNull), optionality (!column.notNull || (column.notNull && column.hasDefault) for inserts, always true for updates), and exclusion (generated.type === 'always').

Sources: drizzle-arktype/src/schema.ts:61-98

Static Type Inference and Refinements

Compile-Time Schema Inference and Utilities

Internal type utilities orchestrate compile-time schema composition, parameter validation, and refinement mapping across select, insert, and update operations. The BuildSchema type maps table columns or view fields into an instantiated ArkType object schema, omitting any columns marked as generated always via ColumnIsGeneratedAlwaysAs.

export type BuildSchema<
	TType extends 'select' | 'insert' | 'update',
	TColumns extends Record<string, any>,
	TRefinements extends Record<string, any> | undefined,
> = type.instantiate<
	Simplify<
		{
			readonly [K in keyof TColumns as ColumnIsGeneratedAlwaysAs<TColumns[K]> extends true ? never : K]:
				TColumns[K] extends infer TColumn extends Column
					? IsRefinementDefined<TRefinements, K> extends true
						? HandleRefinement<TType, TRefinements[K & keyof TRefinements], TColumn>
					: HandleColumn<TType, TColumn>
					: TColumns[K] extends infer TNested extends SelectedFieldsFlat<Column> | Table | View ? BuildSchema<
							TType,
							GetSelection<TNested>,
							TRefinements extends object ? TRefinements[K & keyof TRefinements] : undefined
						>
					: any;
		}
	>
>;

Sources: drizzle-arktype/src/schema.types.internal.ts:43-63

The Conditions interface specifies functional predicates that determine whether columns are excluded, optional, or nullable during schema generation.

export interface Conditions {
	never: (column?: Column) => boolean;
	optional: (column: Column) => boolean;
	nullable: (column: Column) => boolean;
}

Sources: drizzle-arktype/src/schema.types.internal.ts:6-10

Condition UtilityParameter TypeReturn TypePurpose
nevercolumn?: ColumnbooleanDetermines if a column should be omitted entirely from the generated schema
optionalcolumn: ColumnbooleanEvaluates whether a column accepts optional input values
nullablecolumn: ColumnbooleanEvaluates whether a column permits null values

Sources: drizzle-arktype/src/schema.types.internal.ts:6-10

Field Refinement and Validation Typings

User-supplied field refinements are typed and validated through dedicated utility types that ensure type safety and prevent unknown keys. BuildRefine constructs a mapped type over table columns, accepting either a generic ArkType schema, a refinement function returning a generic schema, or nested selections.

export type BuildRefine<
	TColumns extends Record<string, any>,
> = {
	[K in keyof TColumns as TColumns[K] extends Column | SelectedFieldsFlat<Column> | Table | View ? K : never]?:
		TColumns[K] extends Column ? BuildRefineField<GetArktypeType<TColumns[K]>>
			: BuildRefine<GetSelection<TColumns[K]>>;
};

Sources: drizzle-arktype/src/schema.types.internal.ts:16-22

The NoUnknownKeys utility validates refinement keys against comparison schemas, returning a DrizzleTypeError when an unrecognized key is encountered.

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, GenericSchema> ? NoUnknownKeys<TRefinement[K], TCompare[K]>
		: TRefinement[K]
		: DrizzleTypeError<`Found unknown key in refinement: "${K & string}"`>;
};

Sources: drizzle-arktype/src/schema.types.internal.ts:65-73

Caution

Supplying an unrecognized key within a refinement object triggers NoUnknownKeys to evaluate a DrizzleTypeError string literal type, failing compilation.

Sources: drizzle-arktype/src/schema.types.internal.ts:65-73

Refinement Resolution and Operation Handling

The HandleRefinement type processes user-defined refinement functions or static schemas based on the operation context (select, insert, or update). If a refinement is defined as a function, HandleRefinement checks column nullability and applies ArktypeNullable when not strictly not-null, and wraps the resulting schema in ArktypeOptional for update operations.

type HandleRefinement<
	TType extends 'select' | 'insert' | 'update',
	TRefinement,
	TColumn extends Column,
> = TRefinement extends (schema: any) => GenericSchema ? (
		TColumn['_']['notNull'] extends true ? ReturnType<TRefinement>
			: ArktypeNullable<ReturnType<TRefinement>>
	) extends infer TSchema ? TType extends 'update' ? ArktypeOptional<TSchema>
		: TSchema
	: Type<any>
	: TRefinement;

Sources: drizzle-arktype/src/schema.types.internal.ts:24-34

The presence of a refinement for any given key is verified by IsRefinementDefined, which inspects whether the refinement property extends GenericSchema or a valid refinement builder function.

type IsRefinementDefined<
	TRefinements extends Record<string | symbol | number, any> | undefined,
	TKey extends string | symbol | number,
> = TRefinements extends object ? TRefinements[TKey] extends GenericSchema | ((schema: any) => any) ? true
	: false
	: false;

Sources: drizzle-arktype/src/schema.types.internal.ts:36-41

Note

During schema building, IsRefinementDefined acts as a conditional gate: when it evaluates to true, HandleRefinement processes the custom rule; otherwise, standard column rules via HandleColumn take effect.

Sources: drizzle-arktype/src/schema.types.internal.ts:51-54

Specialized Column Schemas and Helpers

Standalone Column Builders and Primitives

The library exports standalone schema definitions and helper utilities designed for validating specific runtime structures, primitive types, and buffer values without requiring a full table configuration. The literalSchema accepts strings, numbers, booleans, and null values, serving as the foundation for broader structural definitions.

export const literalSchema = type.string.or(type.number).or(type.boolean).or(type.null);
export const jsonSchema = literalSchema.or(type.unknown.as<any>().array()).or(type.object.as<Record<string, any>>());
export const bufferSchema = type.unknown.narrow((value) => value instanceof Buffer).as<Buffer>().describe( // eslint-disable-line no-instanceof/no-instanceof
	'a Buffer instance',
);

Sources: drizzle-arktype/src/column.ts:60-64

The jsonSchema composes literalSchema with unknown arrays and arbitrary object records to validate nested JSON payloads. Meanwhile, bufferSchema uses a narrowing check to enforce runtime validation for Buffer instances with a descriptive error string.

export const jsonSchema = literalSchema.or(type.unknown.as<any>().array()).or(type.object.as<Record<string, any>>());
export const bufferSchema = type.unknown.narrow((value) => value instanceof Buffer).as<Buffer>().describe( // eslint-disable-line no-instanceof/no-instanceof
	'a Buffer instance',
);

Sources: drizzle-arktype/src/column.ts:61-64

Helper SchemaUnderlying DefinitionTarget Runtime Structure
literalSchematype.string.or(type.number).or(type.boolean).or(type.null)Primitive scalars and null values
jsonSchemaliteralSchema.or(type.unknown.array()).or(type.object)Nested JSON-compatible data structures
bufferSchematype.unknown.narrow((value) => value instanceof Buffer)Node.js Buffer byte arrays

Sources: drizzle-arktype/src/column.ts:60-64

Enum Constraint Checking and Resolution

Column definitions containing restricted sets of allowed values are processed using isWithEnum to determine whether an explicit enumeration constraint exists. When enum values are present and non-empty, columnToSchema constructs an ArkType enumerated type using type.enumerated(...column.enumValues). If the column lacks explicit values or the array is empty, it falls back to a standard string type.

if (isWithEnum(column)) {
	schema = column.enumValues.length ? type.enumerated(...column.enumValues) : type.string;
}

Sources: drizzle-arktype/src/column.ts:69-71

The utility function isWithEnum verifies column configurations by checking whether the enumValues property exists on the column object, is an array, and contains a positive length. The related isPgEnum helper casts this check to validate PostgreSQL-specific enum entities.

export function isWithEnum(column: Column): column is typeof column & { enumValues: [string, ...string[]] } {
	return 'enumValues' in column && Array.isArray(column.enumValues) && column.enumValues.length > 0;
}
 
export const isPgEnum: (entity: any) => entity is PgEnum<[string, ...string[]]> = isWithEnum as any;

Sources: drizzle-arktype/src/utils.ts:10-14

Caution

Passing a column object with an empty enumValues array will bypass the enumerated schema builder and default to an unrestricted string validator.

Sources: drizzle-arktype/src/column.ts:69-71

Was this page helpful?