drizzle-zod
drizzle-zod
is a plugin for Drizzle ORM that allows you to generate Zod schemas from Drizzle ORM schemas.
Install the dependencies
npm i drizzle-zod
yarn add drizzle-zod
pnpm add drizzle-zod
bun add drizzle-zod
IMPORTANT
This documentation is for [email protected]
and higher
You must also have Drizzle ORM v0.36.0 or greater and Zod v3.0.0 or greater installed.
Select schema
Defines the shape of data queried from the database - can be used to validate API responses.
import { pgTable , text , integer } from 'drizzle-orm/pg-core' ;
import { createSelectSchema } from 'drizzle-zod' ;
const users = pgTable ( 'users' , {
id : integer () .generatedAlwaysAsIdentity () .primaryKey () ,
name : text () .notNull () ,
age : integer () .notNull ()
});
const userSelectSchema = createSelectSchema (users);
const rows = await db .select ({ id : users .id , name : users .name }) .from (users) .limit ( 1 );
const parsed : { id : number ; name : string ; age : number } = userSelectSchema .parse (rows[ 0 ]); // Error: `age` is not returned in the above query
const rows = await db .select () .from (users) .limit ( 1 );
const parsed : { id : number ; name : string ; age : number } = userSelectSchema .parse (rows[ 0 ]); // Will parse successfully
Views and enums are also supported.
import { pgEnum } from 'drizzle-orm/pg-core' ;
import { createSelectSchema } from 'drizzle-zod' ;
const roles = pgEnum ( 'roles' , [ 'admin' , 'basic' ]);
const rolesSchema = createSelectSchema (roles);
const parsed : 'admin' | 'basic' = rolesSchema .parse ( ... );
const usersView = pgView ( 'users_view' ) .as ((qb) => qb .select () .from (users) .where ( gt ( users .age , 18 )));
const usersViewSchema = createSelectSchema (usersView);
const parsed : { id : number ; name : string ; age : number } = usersViewSchema .parse ( ... );
Insert schema
Defines the shape of data to be inserted into the database - can be used to validate API requests.
import { pgTable , text , integer } from 'drizzle-orm/pg-core' ;
import { createInsertSchema } from 'drizzle-zod' ;
const users = pgTable ( 'users' , {
id : integer () .generatedAlwaysAsIdentity () .primaryKey () ,
name : text () .notNull () ,
age : integer () .notNull ()
});
const userInsertSchema = createInsertSchema (users);
const user = { name : 'John' };
const parsed : { name : string , age : number } = userInsertSchema .parse (user); // Error: `age` is not defined
const user = { name : 'Jane' , age : 30 };
const parsed : { name : string , age : number } = userInsertSchema .parse (user); // Will parse successfully
await db .insert (users) .values (parsed);
Update schema
Defines the shape of data to be updated in the database - can be used to validate API requests.
import { pgTable , text , integer } from 'drizzle-orm/pg-core' ;
import { createUpdateSchema } from 'drizzle-zod' ;
const users = pgTable ( 'users' , {
id : integer () .generatedAlwaysAsIdentity () .primaryKey () ,
name : text () .notNull () ,
age : integer () .notNull ()
});
const userUpdateSchema = createUpdateSchema (users);
const user = { id : 5 , name : 'John' };
const parsed : { name ?: string | undefined , age ?: number | undefined } = userUpdateSchema .parse (user); // Error: `id` is a generated column, it can't be updated
const user = { age : 35 };
const parsed : { name ?: string | undefined , age ?: number | undefined } = userUpdateSchema .parse (user); // Will parse successfully
await db .update (users) .set (parsed) .where ( eq ( users .name , 'Jane' ));
Refinements
Each create schema function accepts an additional optional parameter that you can used to extend, modify or completely overwite a field’s schema. Defining a callback function will extend or modify while providing a Zod schema will overwrite it.
import { pgTable , text , integer , json } from 'drizzle-orm/pg-core' ;
import { createSelectSchema } from 'drizzle-zod' ;
import { z } from 'zod' ;
const users = pgTable ( 'users' , {
id : integer () .generatedAlwaysAsIdentity () .primaryKey () ,
name : text () .notNull () ,
bio : text () ,
preferences : json ()
});
const userSelectSchema = createSelectSchema (users , {
name : (schema) => schema .max ( 20 ) , // Extends schema
bio : (schema) => schema .max ( 1000 ) , // Extends schema before becoming nullable/optional
preferences : z .object ({ theme : z .string () }) // Overwrites the field, including its nullability
});
const parsed : {
id : number ;
name : string ,
bio ?: string | undefined ;
preferences : {
theme : string ;
};
} = userSelectSchema .parse ( ... );
Factory functions
For more advanced use cases, you can use the createSchemaFactory
function.
Use case: Using an extended Zod instance
import { pgTable , text , integer } from 'drizzle-orm/pg-core' ;
import { createSchemaFactory } from 'drizzle-zod' ;
import { z } from '@hono/zod-openapi' ; // Extended Zod instance
const users = pgTable ( 'users' , {
id : integer () .generatedAlwaysAsIdentity () .primaryKey () ,
name : text () .notNull () ,
age : integer () .notNull ()
});
const { createInsertSchema } = createSchemaFactory ({ zodInstance : z });
const userInsertSchema = createInsertSchema (users , {
// We can now use the extended instance
name : (schema) => schema .openapi ({ example : 'John' })
});
Data type reference
pg .boolean ();
mysql .boolean ();
sqlite .integer ({ mode : 'boolean' });
// Schema
z .boolean ();
pg .date ({ mode : 'date' });
pg .timestamp ({ mode : 'date' });
mysql .date ({ mode : 'date' });
mysql .datetime ({ mode : 'date' });
mysql .timestamp ({ mode : 'date' });
sqlite .integer ({ mode : 'timestamp' });
sqlite .integer ({ mode : 'timestamp_ms' });
// Schema
z .date ();
pg .date ({ mode : 'string' });
pg .timestamp ({ mode : 'string' });
pg .cidr ();
pg .inet ();
pg .interval ();
pg .macaddr ();
pg .macaddr8 ();
pg .numeric ();
pg .text ();
pg .sparsevec ();
pg .time ();
mysql .binary ();
mysql .date ({ mode : 'string' });
mysql .datetime ({ mode : 'string' });
mysql .decimal ();
mysql .time ();
mysql .timestamp ({ mode : 'string' });
mysql .varbinary ();
sqlite .numeric ();
sqlite .text ({ mode : 'text' });
// Schema
z .string ();
pg .bit ({ dimensions : ... });
// Schema
z .string () .regex ( / ^ [01] +$ / ) .max (dimensions);
pg .uuid ();
// Schema
z .string () .uuid ();
pg .char ({ length : ... });
mysql .char ({ length : ... });
// Schema
z .string () .length (length);
pg .varchar ({ length : ... });
mysql .varchar ({ length : ... });
sqlite .text ({ mode : 'text' , length : ... });
// Schema
z .string () .max (length);
mysql .tinytext ();
// Schema
z .string () .max ( 255 ); // unsigned 8-bit integer limit
mysql .text ();
// Schema
z .string () .max ( 65_535 ); // unsigned 16-bit integer limit
mysql .mediumtext ();
// Schema
z .string () .max ( 16_777_215 ); // unsigned 24-bit integer limit
mysql .longtext ();
// Schema
z .string () .max ( 4_294_967_295 ); // unsigned 32-bit integer limit
pg .text ({ enum : ... });
pg .char ({ enum : ... });
pg .varchar ({ enum : ... });
mysql .tinytext ({ enum : ... });
mysql .mediumtext ({ enum : ... });
mysql .text ({ enum : ... });
mysql .longtext ({ enum : ... });
mysql .char ({ enum : ... });
mysql .varchar ({ enum : ... });
mysql .mysqlEnum ( ... , ... );
sqlite .text ({ mode : 'text' , enum : ... });
// Schema
z .enum (enum);
mysql .tinyint ();
// Schema
z .number () .min ( - 128 ) .max ( 127 ) .int (); // 8-bit integer lower and upper limit
mysql .tinyint ({ unsigned : true });
// Schema
z .number () .min ( 0 ) .max ( 255 ) .int (); // unsigned 8-bit integer lower and upper limit
pg .smallint ();
pg .smallserial ();
mysql .smallint ();
// Schema
z .number () .min ( - 32_768 ) .max ( 32_767 ) .int (); // 16-bit integer lower and upper limit
mysql .smallint ({ unsigned : true });
// Schema
z .number () .min ( 0 ) .max ( 65_535 ) .int (); // unsigned 16-bit integer lower and upper limit
pg .real ();
mysql .float ();
// Schema
z .number () .min ( - 8_388_608 ) .max ( 8_388_607 ); // 24-bit integer lower and upper limit
mysql .mediumint ();
// Schema
z .number () .min ( - 8_388_608 ) .max ( 8_388_607 ) .int (); // 24-bit integer lower and upper limit
mysql .float ({ unsigned : true });
// Schema
z .number () .min ( 0 ) .max ( 16_777_215 ); // unsigned 24-bit integer lower and upper limit
mysql .mediumint ({ unsigned : true });
// Schema
z .number () .min ( 0 ) .max ( 16_777_215 ) .int (); // unsigned 24-bit integer lower and upper limit
pg .integer ();
pg .serial ();
mysql .int ();
// Schema
z .number () .min ( - 2_147_483_648 ) .max ( 2_147_483_647 ) .int (); // 32-bit integer lower and upper limit
mysql .int ({ unsigned : true });
// Schema
z .number () .min ( 0 ) .max ( 4_294_967_295 ) .int (); // unsgined 32-bit integer lower and upper limit
pg .doublePrecision ();
mysql .double ();
mysql .real ();
sqlite .real ();
// Schema
z .number () .min ( - 140_737_488_355_328 ) .max ( 140_737_488_355_327 ); // 48-bit integer lower and upper limit
mysql .double ({ unsigned : true });
// Schema
z .number () .min ( 0 ) .max ( 281_474_976_710_655 ); // unsigned 48-bit integer lower and upper limit
pg .bigint ({ mode : 'number' });
pg .bigserial ({ mode : 'number' });
mysql .bigint ({ mode : 'number' });
mysql .bigserial ({ mode : 'number' });
sqlite .integer ({ mode : 'number' });
// Schema
z .number () .min ( - 9_007_199_254_740_991 ) .max ( 9_007_199_254_740_991 ) .int (); // Javascript min. and max. safe integers
mysql .serial ();
// Schema
z .number () .min ( 0 ) .max ( 9_007_199_254_740_991 ) .int (); // Javascript max. safe integer
pg .bigint ({ mode : 'bigint' });
pg .bigserial ({ mode : 'bigint' });
mysql .bigint ({ mode : 'bigint' });
sqlite .blob ({ mode : 'bigint' });
// Schema
z .bigint () .min ( - 9_223_372_036_854_775_808 n ) .max ( 9_223_372_036_854_775_807 n ); // 64-bit integer lower and upper limit
mysql .bigint ({ mode : 'bigint' , unsigned : true });
// Schema
z .bigint () .min ( 0 ) .max ( 18_446_744_073_709_551_615 n ); // unsigned 64-bit integer lower and upper limit
mysql .year ();
// Schema
z .number () .min ( 1_901 ) .max ( 2_155 ) .int ();
pg .geometry ({ type : 'point' , mode : 'tuple' });
pg .point ({ mode : 'tuple' });
// Schema
z .tuple ([ z .number () , z .number ()]);
pg .geometry ({ type : 'point' , mode : 'xy' });
pg .point ({ mode : 'xy' });
// Schema
z .object ({ x : z .number () , y : z .number () });
pg .halfvec ({ dimensions : ... });
pg .vector ({ dimensions : ... });
// Schema
z .array ( z .number ()) .length (dimensions);
pg .line ({ mode : 'abc' });
// Schema
z .object ({ a : z .number () , b : z .number () , c : z .number () });
pg .line ({ mode : 'tuple' });
// Schema
z .tuple ([ z .number () , z .number () , z .number ()]);
pg .json ();
pg .jsonb ();
mysql .json ();
sqlite .blob ({ mode : 'json' });
sqlite .text ({ mode : 'json' });
// Schema
const self = z .lazy (() => z .union ([ z .union ([ z .string () , z .number () , z .boolean () , z .null ()]) , z .array (self) , z .record (self)]));
sqlite .blob ({ mode : 'buffer' });
// Schema
z .custom < Buffer >((v) => v instanceof Buffer );
pg .dataType () .array ( ... );
// Schema
z .array (baseDataTypeSchema) .length (size);