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

Drizzle Valibot Integration

Last updated Oct 12, 2026100% grounded
Relevant source files

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

Overview

Drizzle Valibot Integration translates Drizzle ORM table definitions, database views, and column specifications into corresponding Valibot runtime validation schemas. It provides factory functions such as createSelectSchema, createInsertSchema, and createUpdateSchema that inspect column nullability, default values, and generation rules to build precise object schemas for querying, inserting, and updating data. Sources: drizzle-valibot/src/schema.ts:58-94

The integration handles column mapping across various SQL dialects, converting primitive types, arrays, JSON objects, enums, and custom types into their Valibot validator equivalents with proper type safety and constraints. Sources: drizzle-valibot/src/column.ts:73-126, drizzle-valibot/src/column.types.ts:42-88

Developers can also supply custom refinements to override or extend individual field validators while maintaining compile-time type inference across operations. Sources: drizzle-valibot/src/schema.ts:28-36, drizzle-valibot/src/schema.types.internal.ts:14-29

Public API surface

Entry-Point Exports and Module Structure

The drizzle-valibot package exposes its public API through a main entry point defined in drizzle-valibot/src/index.ts. The primary exports include explicit column utility schemas (bufferSchema, jsonSchema, literalSchema) alongside star exports for column types, schema factories, internal schema types, and general utility functions.

export { bufferSchema, jsonSchema, literalSchema } from './column.ts';
export * from './column.types.ts';
export * from './schema.ts';
export * from './schema.types.internal.ts';
export * from './schema.types.ts';
export * from './utils.ts';

Sources: drizzle-valibot/src/index.ts:1-6

Note

The entry-point exports are bundled to support both ESM and CommonJS module resolution paths via conditional exports in package configuration.

Package Configuration and Exports Map

The module distribution is governed by drizzle-valibot/package.json, which specifies dual CJS and ESM output paths, type definitions, peer dependencies on drizzle-orm (>=0.36.0) and valibot (>=1.0.0-beta.7), and provenance publishing configuration.

FieldTarget / ValuePurpose
main./index.cjsCommonJS entry point
module./index.mjsECMAScript module entry point
types./index.d.tsTypeScript type definitions
peerDependenciesdrizzle-orm, valibotRequired host library versions
publishConfigprovenance: trueCryptographic provenance for npm publishing

Sources: drizzle-valibot/package.json:14-33

Schema generation architecture

Entity Dispatch and Column Resolution

The runtime schema generation process begins by resolving the input entity—whether a table, view, or PostgreSQL enum—into its constituent column definitions or selected fields. The helper function getColumns inspects the entity using isTable, returning columns via getTableColumns if it represents a table or getViewSelectedFields if it represents a view. For PostgreSQL enums identified via isPgEnum, createSelectSchema bypasses column iteration entirely, mapping the enum's string values directly to a Valibot enum validator using mapEnumValues.

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

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

export const createSelectSchema: CreateSelectSchema = (
	entity: Table | View | PgEnum<[string, ...string[]]>,
	refine?: Record<string, any>,
) => {
	if (isPgEnum(entity)) {
		return v.enum(mapEnumValues(entity.enumValues));
	}
	const columns = getColumns(entity);
	return handleColumns(columns, refine ?? {}, {
		never: () => false,
		optional: () => false,
		nullable: (column) => !column.notNull,
	}) as any;
};

Sources: drizzle-valibot/src/schema.ts:58-71

Operation-Specific Condition Strategies

The core schema builder function handleColumns iterates over column records while accepting a Conditions interface that dictates how individual column properties translate into Valibot wrappers for select, insert, and update operations. Each factory injects a specific condition strategy:

Factory Functionnever Conditionoptional Conditionnullable Condition
createSelectSchema() => false() => false(column) => !column.notNull
createInsertSchema`(column) => column?.generated?.type === 'always'column?.generatedIdentity?.type === 'always'`
createUpdateSchema`(column) => column?.generated?.type === 'always'column?.generatedIdentity?.type === 'always'`

Sources: drizzle-valibot/src/schema.ts:66-70, drizzle-valibot/src/schema.ts:78-82, drizzle-valibot/src/schema.ts:90-94

Note

Columns with database-generated values (generated?.type === 'always' or generatedIdentity?.type === 'always') return true for the never condition during insert and update operations, causing them to be entirely omitted from the resulting Valibot object schema.

export const createInsertSchema: 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;
};

Sources: drizzle-valibot/src/schema.ts:73-83

export const createUpdateSchema: 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;
};

Sources: drizzle-valibot/src/schema.ts:85-95

Recursive Handling of Nested Relations and Refinements

When handleColumns encounters nested object properties, aliased SQL expressions, or relational selections rather than raw column instances, it recursively evaluates sub-schemas. If a static refinement value is provided for a key, it bypasses column conversion and assigns the replacement schema directly. Otherwise, it generates the base schema via columnToSchema, applies any functional refinement, evaluates exclusion via conditions.never, and wraps the resulting validator with v.nullable or v.optional based on the active condition strategy.

function handleColumns(
	columns: Record<string, any>,
	refinements: Record<string, any>,
	conditions: Conditions,
): v.GenericSchema {
	const columnSchemas: Record<string, v.GenericSchema> = {};
 
	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;
		}
 
		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) : v.any();
		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] = v.nullable(columnSchemas[key]!);
			}
 
			if (conditions.optional(column)) {
				columnSchemas[key] = v.optional(columnSchemas[key]!);
			}
		}
	}
 
	return v.object(columnSchemas) as any;
}

Sources: drizzle-valibot/src/schema.ts:14-56

Column mapping engine

Enum and Geometric Type Dispatch

The column mapping engine translates individual Drizzle column definitions into Valibot primitive and structured validators by examining column properties and type metadata. The primary entry point function columnToSchema first checks whether a column possesses an enumeration configuration via isWithEnum. If enumeration values are present, it transforms them using mapEnumValues into a Valibot enum validator, falling back to a standard string validator if the enumeration is empty.

export function columnToSchema(column: Column): v.GenericSchema {
	let schema!: v.GenericSchema;
 
	if (isWithEnum(column)) {
		schema = column.enumValues.length ? v.enum(mapEnumValues(column.enumValues)) : v.string();
	}

Sources: drizzle-valibot/src/column.ts:73-78

For columns lacking enums, columnToSchema dispatches specialized PostgreSQL geometric and vector types by matching their internal column type identifiers through isColumnType. Geometric pairs like point tuples and geometries are mapped to a two-element number tuple, while point and geometry objects map to an x and y coordinate object. Vector types map to a number array, adding a Valibot length pipe validator if explicit dimensions are defined.

Column Type MatcherIdentifiers CheckedGenerated Valibot Schema Structure
PgGeometry / PgPointTuple['PgGeometry', 'PgPointTuple']v.tuple([v.number(), v.number()])
PgGeometryObject / PgPointObject['PgGeometryObject', 'PgPointObject']v.object({ x: v.number(), y: v.number() })
PgHalfVector / PgVector['PgHalfVector', 'PgVector']v.array(v.number()) (with optional length pipe)
PgLine (Tuple)['PgLine']v.tuple([v.number(), v.number(), v.number()])
PgLineABC (Object)['PgLineABC']v.object({ a: v.number(), b: v.number(), c: v.number() })

Sources: drizzle-valibot/src/column.ts:82-95

Note

Vector and array column generators inspect column.dimensions or column.size properties respectively, attaching a Valibot length validation constraint dynamically if a fixed size constraint is set on the Drizzle column builder.

} else if (isColumnType<PgHalfVector<any> | PgVector<any>>(column, ['PgHalfVector', 'PgVector'])) {
	schema = v.array(v.number());
	schema = column.dimensions ? v.pipe(schema as v.ArraySchema<any, any>, v.length(column.dimensions)) : schema;

Sources: drizzle-valibot/src/column.ts:88-90

Scalar and Composite Data Type Mapping

When handling standard data categories, the mapping engine inspects column.dataType to route execution to appropriate helper functions or primitive constructors. Array columns recursively map their base column definition or fall back to an unconstrained v.any() array if untyped. Numerical, bigint, and string columns delegate to dedicated conversion functions that parse SQL type options, while boolean, date, json, custom, and buffer types map directly to their Valibot equivalents using shared constants like jsonSchema and bufferSchema.

else if (isColumnType<PgArray<any, any>>(column, ['PgArray'])) {
	schema = v.array(columnToSchema(column.baseColumn));
	schema = column.size ? v.pipe(schema as v.ArraySchema<any, any>, v.length(column.size)) : schema;
} else if (column.dataType === 'array') {
	schema = v.array(v.any());
} else if (column.dataType === 'number') {
	schema = numberColumnToSchema(column);
} else if (column.dataType === 'bigint') {
	schema = bigintColumnToSchema(column);
} else if (column.dataType === 'boolean') {
	schema = v.boolean();
} else if (column.dataType === 'date') {
	schema = v.date();
} else if (column.dataType === 'string') {
	schema = stringColumnToSchema(column);
} else if (column.dataType === 'json') {
	schema = jsonSchema;
} else if (column.dataType === 'custom') {
	schema = v.any();
} else if (column.dataType === 'buffer') {
	schema = bufferSchema;
}

Sources: drizzle-valibot/src/column.ts:97-118

export const literalSchema = v.union([v.string(), v.number(), v.boolean(), v.null()]);
export const jsonSchema: v.GenericSchema<Json> = v.union([
	literalSchema,
	v.array(v.any()),
	v.record(v.string(), v.any()),
]);
export const bufferSchema: v.GenericSchema<Buffer> = v.custom<Buffer>((v) => v instanceof Buffer); // eslint-disable-line no-instanceof/no-instanceof

Sources: drizzle-valibot/src/column.ts:61-67

If a column matches none of the recognized database types or data categories, columnToSchema defaults the schema to v.any() as a safe fallback.

if (!schema) {
	schema = v.any();
}
 
return schema;

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

Static type inference

Schema Inference and Property Extraction

Static type inference in drizzle-valibot maps Drizzle column configurations and table shapes directly to Valibot schema type arguments at compile time. The conditional type ExtractAdditionalProperties inspects a column's internal type definition (columnType) to extract maximum lengths, dimensions, or array sizes. For example, character and text columns from PostgreSQL, MySQL, SQLite, and SingleStore resolve to their respective length bounds, while vector columns extract their dimension counts.

export type ExtractAdditionalProperties<TColumn extends Column> = {
	max: TColumn['_']['columnType'] extends 'PgVarchar' | 'SQLiteText' | 'PgChar' | 'MySqlChar' | 'SingleStoreChar'
		? Assume<TColumn['_'], { length: number | undefined }>['length']
		: TColumn['_']['columnType'] extends 'MySqlText' | 'MySqlVarChar' | 'SingleStoreText' | 'SingleStoreVarChar'
			? number
		: TColumn['_']['columnType'] extends 'PgBinaryVector' | 'PgHalfVector' | 'PgVector'
			? Assume<TColumn['_'], { dimensions: number }>['dimensions']
		: TColumn['_']['columnType'] extends 'PgArray' ? Assume<TColumn['_'], { size: number | undefined }>['size']
		: undefined;
	fixedLength: TColumn['_']['columnType'] extends
		'PgChar' | 'PgHalfVector' | 'PgVector' | 'PgArray' | 'MySqlChar' | 'SingleStoreChar' ? true
		: false;
};

Sources: drizzle-valibot/src/column.types.ts:12-24

Column Type GroupChecked Column TypesExtracted Property Type
Character LimitsPgVarchar, SQLiteText, PgChar, MySqlChar, SingleStoreCharlength (number | undefined)
Text / VarChar SizesMySqlText, MySqlVarChar, SingleStoreText, SingleStoreVarCharnumber
Vector DimensionsPgBinaryVector, PgHalfVector, PgVectordimensions (number)
Array SizesPgArraysize (number | undefined)

Sources: drizzle-valibot/src/column.types.ts:13-20

Note

The helper HasBaseColumn evaluates whether a column possesses a defined base column reference, enabling nested array type resolution for multi-dimensional array structures.

export type HasBaseColumn<TColumn> = TColumn extends { _: { baseColumn: Column | undefined } }
	? IsNever<TColumn['_']['baseColumn']> extends false ? true
	: false
	: false;

Sources: drizzle-valibot/src/column.types.ts:5-8

Type Mapping and Nullability Handlers

The central type-level mapping function GetValibotType processes the extracted data types, column types, enum definitions, and base columns to yield the exact Valibot schema shape. Depending on whether a column enforces a default value or allows nulls, secondary wrapper utilities adjust the final inferred type. Specifically, HandleSelectColumn, HandleInsertColumn, and HandleUpdateColumn wrap underlying Valibot schemas with v.NullableSchema or v.OptionalSchema conditional containers.

type HandleSelectColumn<
	TSchema extends v.GenericSchema,
	TColumn extends Column,
> = TColumn['_']['notNull'] extends true ? TSchema
	: v.NullableSchema<TSchema, undefined>;
 
type HandleInsertColumn<
	TSchema extends v.GenericSchema,
	TColumn extends Column,
> = TColumn['_']['notNull'] extends true
	? TColumn['_']['hasDefault'] extends true ? v.OptionalSchema<TSchema, undefined>
	: TSchema
	: v.OptionalSchema<v.NullableSchema<TSchema, undefined>, undefined>;
 
type HandleUpdateColumn<
	TSchema extends v.GenericSchema,
	TColumn extends Column,
> = TColumn['_']['notNull'] extends true ? v.OptionalSchema<TSchema, undefined>
	: v.OptionalSchema<v.NullableSchema<TSchema, undefined>, undefined>;
 

Sources: drizzle-valibot/src/column.types.ts:133-152

Warning

HandleInsertColumn and HandleUpdateColumn mark columns with defaults or nullable constraints as optional, which changes how partial payloads validate during insert and update operations.

type HandleInsertColumn<
	TSchema extends v.GenericSchema,
	TColumn extends Column,
> = TColumn['_']['notNull'] extends true
	? TColumn['_']['hasDefault'] extends true ? v.OptionalSchema<TSchema, undefined>
	: TSchema
	: v.OptionalSchema<v.NullableSchema<TSchema, undefined>, undefined>;
 
type HandleUpdateColumn<
	TSchema extends v.GenericSchema,
	TColumn extends Column,
> = TColumn['_']['notNull'] extends true ? v.OptionalSchema<TSchema, undefined>
	: v.OptionalSchema<v.NullableSchema<TSchema, undefined>, undefined>;
 

Sources: drizzle-valibot/src/column.types.ts:139-152

Furthermore, BuildSchema iterates over table columns while automatically omitting columns designated with ColumnIsGeneratedAlwaysAs. It resolves custom user-provided refinements through IsRefinementDefined and HandleRefinement, guaranteeing that overridden fields preserve correct nullability and optionality modifiers in the final compiled object schema.

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

Sources: drizzle-valibot/src/schema.types.internal.ts:50-71

Custom schema overrides

Refinement Dispatch and Execution

Schema generation functions (createSelectSchema, createInsertSchema, and createUpdateSchema) accept an optional second argument refine, represented as a record of column keys mapped to either direct Valibot validator schemas or transformer functions. During schema compilation, handleColumns iterates over table columns and inspects any corresponding entry in the refinements dictionary. If a refinement value is provided and is not a function, it directly replaces the generated column schema. If the refinement is instead a function, it receives the automatically generated Valibot schema as its argument, allowing developers to wrap, constrain, or transform the base validator.

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) : v.any();
const refined = typeof refinement === 'function' ? refinement(schema) : schema;

Sources: drizzle-valibot/src/schema.ts:28-36

Note

When nesting occurs across relations, views, or embedded object structures, handleColumns recursively processes child objects and passes down corresponding nested refinement dictionaries via refinements[key] ?? {}.

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-valibot/src/schema.ts:22-26

Handling Nullability and Optionality in Refinements

After applying a custom refinement function or static schema replacement, handleColumns evaluates table-level and column-level conditions to enforce database constraints. Even when a column schema is overridden, subsequent checks verify whether the underlying column is nullable or optional. If the column allows nulls according to the active condition set, the refined schema is automatically wrapped using v.nullable(). Similarly, if conditions specify optionality for inserts or updates, an optional wrapper is applied.

if (column) {
	if (conditions.nullable(column)) {
		columnSchemas[key] = v.nullable(columnSchemas[key]!);
	}
 
	if (conditions.optional(column)) {
		columnSchemas[key] = v.optional(columnSchemas[key]!);
	}
}

Sources: drizzle-valibot/src/schema.ts:44-52

Warning

Generated columns whose generation type is set to 'always' or identity columns configured as 'always' trigger the conditions.never check, causing the column to be omitted entirely from the generated object schema regardless of any supplied refinement.

if (conditions.never(column)) {
	continue;
} else {
	columnSchemas[key] = refined;
}

Sources: drizzle-valibot/src/schema.ts:38-42

Was this page helpful?