Relevant source files
The following files were used as context for generating this wiki page:
- drizzle-valibot/src/column.types.ts
- drizzle-orm/type-tests/mysql/tables.ts
- drizzle-valibot/src/column.ts
- drizzle-orm/type-tests/singlestore/tables.ts
- drizzle-kit/src/snapshotsDiffer.ts
- drizzle-valibot/src/schema.ts
- drizzle-kit/src/serializer/gelSchema.ts
- drizzle-kit/src/serializer/pgSchema.ts
- drizzle-kit/src/introspect-pg.ts
- drizzle-orm/type-tests/mysql/1000columns.ts
- drizzle-kit/src/introspect-mysql.ts
- drizzle-kit/src/introspect-gel.ts
- drizzle-typebox/src/schema.ts
- drizzle-valibot/src/schema.types.internal.ts
- drizzle-kit/src/serializer/mysqlSchema.ts
- drizzle-kit/src/introspect-singlestore.ts
- drizzle-zod/src/schema.ts
- drizzle-valibot/src/index.ts
- drizzle-typebox/src/column.ts
- drizzle-seed/src/index.ts
- drizzle-kit/src/serializer/studio.ts
- drizzle-kit/src/serializer/sqliteSchema.ts
- drizzle-valibot/package.json
- drizzle-kit/src/serializer/singlestoreSchema.ts
- drizzle-valibot/src/utils.ts
- drizzle-kit/src/serializer/singlestoreSerializer.ts
- drizzle-kit/src/schemaValidator.ts
- drizzle-typebox/src/schema.types.internal.ts
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.
| Field | Target / Value | Purpose |
|---|---|---|
main | ./index.cjs | CommonJS entry point |
module | ./index.mjs | ECMAScript module entry point |
types | ./index.d.ts | TypeScript type definitions |
peerDependencies | drizzle-orm, valibot | Required host library versions |
publishConfig | provenance: true | Cryptographic 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 Function | never Condition | optional Condition | nullable 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 Matcher | Identifiers Checked | Generated 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-instanceofSources: 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 Group | Checked Column Types | Extracted Property Type |
|---|---|---|
| Character Limits | PgVarchar, SQLiteText, PgChar, MySqlChar, SingleStoreChar | length (number | undefined) |
| Text / VarChar Sizes | MySqlText, MySqlVarChar, SingleStoreText, SingleStoreVarChar | number |
| Vector Dimensions | PgBinaryVector, PgHalfVector, PgVector | dimensions (number) |
| Array Sizes | PgArray | size (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
Related
On This Page
OverviewPublic API surfaceEntry-Point Exports and Module StructurePackage Configuration and Exports MapSchema generation architectureEntity Dispatch and Column ResolutionOperation-Specific Condition StrategiesRecursive Handling of Nested Relations and RefinementsColumn mapping engineEnum and Geometric Type DispatchScalar and Composite Data Type MappingStatic type inferenceSchema Inference and Property ExtractionType Mapping and Nullability HandlersCustom schema overridesRefinement Dispatch and ExecutionHandling Nullability and Optionality in RefinementsRelated