chrishoermann/zod-prisma-types(opens in a new tab)
Language: TypeScript
Generator creates zod types for your prisma models with advanced validation
- stars
- 869
- watchers
- 5
- forks
- 75
- open issues
- 102
- Last updated
- 01/29/2026
Language: TypeScript
Generator creates zod types for your prisma models with advanced validation
zod-prisma-types is a generator for prisma that generates zod schemas from your prisma models. This includes schemas of models, enums, inputTypes, argTypes, filters and so on. It also provides options to write advanced zod validators directly in the Prisma schema comments.
Since I'm maintaining the generator in my spare time consider buying me a coffee or sponsoring me if you like the project. Thanks!
🕐 Limited Maintenance Mode
Due to time constraints, this package will only receive critical bug fixes and essential updates. I no longer use Prisma as extensively in my projects, which means I don't personally benefit from new generator features that much anymore.
🚀 Recommendation for New Projects
For new projects, please consider using prisma-zod-generator by Omar Dulaimi. Omar maintains multiple Prisma generators and, I believe, has much deeper expertise in this domain than I do. It also seems to have quite similar features.
💡 Why the Alternative?
Omar's active involvement with multiple Prisma generators means better feature coverage, more frequent updates, and deeper integration with the Prisma ecosystem.
📦 Current Status: Maintenance mode - critical fixes only
🔄 Recommended Alternative: prisma-zod-generator
👤 Maintainer: Omar Dulaimi
Make shure
zodis installed as adependencyand not adevDependencyfor the generator to pick it up correctly!
Be aware that some generator options have been removed, a few new ones have been added, the behavior of custom imports has changed and ts-morph is no longer needed to generate files in v2.0.0.
whereUnique inputs. If you have used custom imports in v2.x.x you should change the syntax to the new one. Please read the custom imports section for more information.validateWhereUniqueInputs is now true by default
- Lowercase model names will result in Errors and duplicate schema names in the generated schemas. Please use uppercase model names and prismas
@@map()directive when using lowercase table names in the datebase to avoid this issue.- The
satisfiesoperator: As some people have pointed out theinputandoutputschemas and some of therelationschemas are typed asz.ZodType<myType>. This is required by zod for recursice types to work properly as stated in the docs. This has the downside that some zod methods like.merge(),.omit(), etc. are not available on these types.
tsconfig.jsonuseMultipleFilesoutputwriteBarrelFilescreateInputTypescreateModelTypesaddInputTypeValidationaddIncludeTypeaddSelectTypevalidateWhereUniqueInputcreateOptionalDefaultValuesTypescreateRelationValuesTypescreatePartialTypesuseDefaultValidatorscoerceDatewriteNullishInModelTypesuseTypeAssertionsprismaClientPathzod-prisma
For one of my projects I was in need of a generator that offers the possibility of adding zod validators directly in prisma schema's rich-comments and generates zod schemas for all prisma models, enums, inputTypes, argTypes, filters and so on. I also wanted to be able to import these schemas in the frontend e.g. for form validation and make the generator as flexible as possible so it covers a large range of use cases. Since there were no generators out there that met my requirements or they weren't actively maintained at that time I decided to write zod-prisma-type.
via npm:
npm install zod-prisma-types
via yarn:
yarn add zod-prisma-types
via pnpm:
pnpm add zod-prisma-types
For the generator to work properly you have to set the following options in your tsconfg.json:
strict: true, strictNullChecks: true,
Supports prisma 4.x - 5.x
Add the following code to your schema.prisma file:
generator zod { provider = "zod-prisma-types" }
Then run the following command to create a single index.ts file in the ./generated/zod output folder containing all the zod prisma schemas:
npx prisma generate zod
Then import the schema's into your file:
import { mySchema } from '/prisma/generated/zod'; // All schemas are here by default, use the 'output' option to change it
If you encounter errors like the following
/bin/sh: zod-prisma-types: command not foundplease try to use thenpxcommand with thezod-prisma-typescommand.
generator zod { provider = "npx zod-prisma-types" }
If you want to customize the behavior of the generator you can use the following options:
generator zod { provider = "ts-node-dev ../generator/src/bin.ts" output = "./generated/zod" // default is ./generated/zod useMultipleFiles = true // default is false writeBarrelFiles = false // default is true createInputTypes = false // default is true createModelTypes = false // default is true addInputTypeValidation = false // default is true addIncludeType = false // default is true addSelectType = false // default is true validateWhereUniqueInput = false // default is true createOptionalDefaultValuesTypes = true // default is false createRelationValuesTypes = true // default is false createPartialTypes = true // default is false useDefaultValidators = false // default is true coerceDate = false // default is true writeNullishInModelTypes = true // default is false prismaClientPath = "./path/to/prisma/client" // default is client output path }
default:
false
If you want to create multiple files instead of a single index.ts file you can set this option to true. This will create a file for each model, enum, inputType, argType, filter, etc. The files will be created in sub folders in the specified output folder and a barrel file will be added at the root of the output folder.
generator zod { // ...rest of config useMultipleFiles = false }
default:
./generated/zod
Provide an alternative output path.
default:
true
If you use useMultipleFiles and do not want to create a barrel file for each sub folder you can set this option to false. This will create an index.ts file in each sub folder that exports all the files in the folder. This option may be beneficial for typescript performance on big schemas.
generator zod { // ...rest of config writeBarrelFiles = false }
default:
true
If you just want to create zod schemas for your models and enums you can disable the creation of the corresponding input types. This may be useful if you just want to use zod schemas of your models for validating input types in react-hook-form or some similar use cases.
generator zod { // ...rest of config createInputTypes = false }
default:
true
If you just want to create zod schemas for your input types you can disable the creation of the corresponding model schemas. This may be useful if you just want to use the zod input schemas for autocompletion in your trpc queries or similar use cases.
generator zod { // ...rest of config createModelTypes = false }
default:
true
If you want to use your custom zod validators that you added via rich-comments only on your generated model schemas but not on your created input type schemas (UserCreateInput, UserUpdateManyInput, etc.) you can disable this feature.
generator zod { // ...rest of config addInputTypeValidation = false }
default:
true
By default the include type is added to the [Model]ArgTypeSchema. If you don't want to add a zod schema for the include type you can set this option to false.
generator zod { // ...rest of config addIncludeType = false }
default:
true
By default the select type is added to the [Model]ArgTypeSchema. If you don't want to add a zod schema for the select type you can set this option to false.
generator zod { // ...rest of config addSelectType = false }
default:
false
By default the generator will not validate the whereUnique input types in multifile mode since a bunch of unused imports will often be generated. If you want to validate the whereUnique input types you can set this option to true.
Be aware that this can lead to eslint errors if you use the
no-unused-varsrule which you need to resolve manually.
generator zod { // ...rest of config validateWhereUniqueInput = true }
default:
false
If you want to have a schema of your model where fields with default values are marked as .optional() you can pass the following config option:
generator zod { // ...rest of config createOptionalDefaultValuesTypes = true } model ModelWithDefaultValues { id Int @id @default(autoincrement()) string String @default("default") otherString String int Int @default(1) otherInt Int float Float @default(1.1) otherFloat Float boolean Boolean @default(true) otherBool Boolean date DateTime @default(now()) otherDate DateTime }
The above model would then generate the following model schemas:
export const ModelWithDefaultValuesSchema = z.object({ id: z.number(), string: z.string(), otherString: z.string(), int: z.number(), otherInt: z.number(), float: z.number(), otherFloat: z.number(), boolean: z.boolean(), otherBool: z.boolean(), date: z.date(), otherDate: z.date(), }); export const ModelWithDefaultValuesOptionalDefaultsSchema = ModelWithDefaultValuesSchema.merge( z.object({ id: z.number().optional(), string: z.string().optional(), int: z.number().optional(), float: z.number().optional(), boolean: z.boolean().optional(), date: z.date().optional(), }), );
default:
false
If you need a separate model type that includes all the relation fields you can pass the following option. Due to the type annotation, that is needed to have recursive types, this model has some limitations since z.ZodType<myType> does not allow some object methods like .merge(), .omit(), etc.
generator zod { // ...rest of config createRelationValuesTypes = true } model User { id String @id @default(cuid()) email String @unique name String? posts Post[] profile Profile? role Role[] @default([USER, ADMIN]) enum AnotherEnum @default(ONE) scalarList String[] lat Float lng Float location Location? @relation(fields: [lat, lng], references: [lat, lng]) }
The above model would generate the following model schemas:
export const UserSchema = z.object({ role: RoleSchema.array(), enum: AnotherEnumSchema, id: z.string().cuid(), email: z.string(), name: z.string().optional(), scalarList: z.string().array(), lat: z.number(), lng: z.number(), }); export type UserRelations = { posts: PostWithRelations[]; profile?: ProfileWithRelations | null; location?: LocationWithRelations | null; }; export type UserWithRelations = z.infer<typeof UserSchema> & UserRelations; export const UserWithRelationsSchema: z.ZodType<UserWithRelations> = UserSchema.merge( z.object({ posts: z.lazy(() => PostWithRelationsSchema).array(), profile: z.lazy(() => ProfileWithRelationsSchema).nullish(), location: z.lazy(() => LocationWithRelationsSchema).nullish(), }), );
If the option is combined with createOptionalDefaultValuesTypes additionally the following model schemas are generated:
export type UserOptionalDefaultsWithRelations = z.infer< typeof UserOptionalDefaultsSchema > & UserRelations; export const UserOptionalDefaultsWithRelationsSchema: z.ZodType<UserOptionalDefaultsWithRelations> = UserOptionalDefaultsSchema.merge( z.object({ posts: z.lazy(() => PostWithRelationsSchema).array(), profile: z.lazy(() => ProfileWithRelationsSchema).nullable(), location: z.lazy(() => LocationWithRelationsSchema).nullable(), target: z.lazy(() => LocationWithRelationsSchema).nullable(), }), );
default:
false
If you need a separate model type that includes all the fields as optional you can pass the following option.
generator zod { // ...rest of config createPartialTypes = true } model User { id String @id @default(cuid()) email String @unique name String? posts Post[] profile Profile? role Role[] @default([USER, ADMIN]) enum AnotherEnum @default(ONE) scalarList String[] lat Float lng Float location Location? @relation(fields: [lat, lng], references: [lat, lng]) }
The above model would generate the following model schemas:
export const UserPartialSchema = z .object({ role: RoleSchema.array(), enum: AnotherEnumSchema, id: z.string().cuid(), email: z.string().email({ message: 'Invalid email address' }), name: z.string().min(1).max(100).nullable(), scalarList: z.string().array(), lat: z.number(), lng: z.number(), }) .partial();
When using this option in combination with createRelationValuesTypes the following model schemas are also generated. Due do the type annotation, that is needed to have recursive types, this model has some limitations since z.ZodType<myType> does not allow some object methods like .merge(), .omit(), etc.
export type UserPartialRelations = { posts?: PostPartialWithRelations[]; profile?: ProfilePartialWithRelations | null; location?: LocationPartialWithRelations | null; }; export type UserPartialWithRelations = z.infer<typeof UserPartialSchema> & UserPartialRelations; export const UserPartialWithRelationsSchema: z.ZodType<UserPartialWithRelations> = UserPartialSchema.merge( z.object({ posts: z.lazy(() => PostPartialWithRelationsSchema).array(), profile: z.lazy(() => ProfilePartialWithRelationsSchema).nullable(), location: z.lazy(() => LocationPartialWithRelationsSchema).nullable(), }), ).partial(); export type UserPartial = z.infer<typeof UserPartialSchema>;
default:
true
In certain use cases the generator adds default validators:
model WithDefaultValidators { id String @id @default(cuid()) idTwo String @default(uuid()) integer Int }
export const WithDefaultValidatorsSchema = z.object({ id: z.string().cuid(), idTwo: z.string().uuid(), integer: z.number().int(), });
These defaults are overwritten when using a custom validator (see: Field Validators) or when you opt out of using a default validator on a specific field:
model WithDefaultValidators { id String @id @default(cuid()) /// @zod.string.noDefault() idTwo String @default(uuid()) /// @zod.string.noDefault() integer Int /// @zod.number.noDefault() }
export const WithDefaultValidatorsSchema = z.object({ id: z.string(), idTwo: z.string(), integer: z.number(), });
You can opt out of this feature completly by passing false to the config option.
generator zod { // ...rest of config useDefaultValidators = false }
More default validators are planned in future releases (by checking the @db. fields in the schema). If you have some ideas for default validators feel free to open an issue.
default: true
Per default DateTime values are coerced to Date objects as long as you pass in a valid ISO string or an instance of Date. You can change this behavior to generate a simple z.date() by passing the following option to the generator config:
generator zod { // ...rest of config coerceDate = false }
default: false
By default the generator just writes .nullable() in the modelTypes when a field in the Prisma type is nullable. If you want these fields to accept null | undefined, which would be represented by .nullish() in the schema, you can pass the following option to the generator config:
generator zod { // ...rest of config writeNullishInModelTypes = true }
default: false
Since there is a known typscript error when using zod greater than 3.21.1 you can now use type assertions to circumvent the error and use the latest version of zod.
A WORD OF WARNING: Use this solution at your own risk! Because type assertions are circumventing the type system this can lead to unexpected behavior and runtime errors. But since the types worked in
zodversion3.21.1and the error is a known issue inzodversion3.21.2I think it might be a valid solution until the issue is resolved.
generator zod { // ...rest of config useTypeAssertions = true }
default:
inferred from prisma schema path
By default the prisma client path is inferred from the output path provided in the prisma.schema file under generator client. If you still need to use a custom path you can pass it to the generator config via this option. A custom path takes precedence over the inferred prisma client output path.
generator zod { // ...rest of config prismaClientPath = "./path/to/prisma/client" }
You can skip schema generation based on e.g. the environment you are currently working in. For example you can only generate the schemas when you're in development but not when you run generation in production (because in production the schemas would already have been created and pushed to the server via your git repo). To skip generation in an environment just add the following environment variable to your respective .env files:
SKIP_ZOD_PRISMA = 'true';
For custom enums a separate type is generated that represents the enum values as a union. Since in typescript unions are more useful than enums this can come in handy.
enum MyEnum { A B C }
export const MyEnumSchema = z.nativeEnum(PrismaClient.MyEnum); export type MyEnumType = `${z.infer<typeof MyEnumSchema>}`; // union of "A" | "B" | "C"
When using json null values prisma has a unique way of handling Database NULL and JSON null as stated in the Docs.
To adhere to this concept you can pass "DbNull" or "JsonNull" as string to a nullable Json field. When the schema gets validated these strings are transformed to Prisma.DbNull or Prisma.JsonNull to satisfy the prisma.[myModel].create() | .update() | ... functions.
This transformation is only applicable for input schemas like
[myModel]CreateInputSchema, [myModel]UpdateInputSchema, .... Since the model schemas represent the return value from the database - they can havenullvalues - they are not affected by this transformation.
const parsedJsonSchema = myJsonSchema.parse({ myJsonField: 'DbNull', // or "JsonNull" }); // will be transformed to: const parsedJsonSchema = { myJsonField: Prisma.DbNull, // or Prisma.JsonNull };
Decimals are a special case since they are not supported by zod out of the box. Therefore the generator utilizes the Prisma.Decimal class and the DecimalJsLike type from the @prisma/client package and - if installed - the decimal.js package.
A downside of this approach is that
Prismacan't be simply imported as a type anymore because it is used to determine if an instance ofPrisma.Decimalis passed in.
When using Decimal a refine method is used to validate if the input adheres to the prisma input union string | number | Decimal | DecimalJsLike.
model MyModel { id Int @id @default(autoincrement()) decimal Decimal }
The above model would generate the following helper schemas that are used to validate the input in the create and update methods:
// DECIMAL HELPERS //------------------------------------------------------ export const DecimalJSLikeSchema: z.ZodType<Prisma.DecimalJsLike> = z.object({ d: z.array(z.number()), e: z.number(), s: z.number(), toFixed: z.function(z.tuple([]), z.string()), }); export const DECIMAL_STRING_REGEX = /^[0-9.,e+-bxffo_cp]+$|Infinity|NaN/; export const isValidDecimalInput = ( v?: null | string | number | Prisma.DecimalJsLike, ): v is string | number | Prisma.DecimalJsLike => { if (v === undefined || v === null) return false; return ( (typeof v === 'object' && 'd' in v && 'e' in v && 's' in v && 'toFixed' in v) || (typeof v === 'string' && DECIMAL_STRING_REGEX.test(v)) || typeof v === 'number' ); };
The input schemas reflect the types that are passed to prismas create and update methods.
These schemas further validate the input and throw an error if the input is not valid. A downside of this approach is that Prisma can't be simply imported as a type anymore because it is used to determine if an instance of Prisma.Decimal is passed in.
If
decimal.jsis installed the schema also validates if the input is a validdecimal.jsinstance.
// INPUT TYPES //------------------------------------------------------ import { Prisma } from '@prisma/client'; // can't be imported as type because of "instance of Prisma.Decimal" check import Decimal from 'decimal.js'; // gets added if installed import { z } from 'zod'; import { isValidDecimalInput } from './isValidDecimalInput'; import { DecimalJSLikeSchema } from './DecimalJsLikeSchema'; export const DecimalModelCreateInputSchema: z.ZodType<Prisma.DecimalModelCreateInput> = z .object({ decimal: z .union([ z.number(), z.string(), z.instanceof(Decimal), z.instanceof(Prisma.Decimal), DecimalJSLikeSchema, ]) .refine((v) => isValidDecimalInput(v), { message: 'Must be a Decimal', }), decimalOpt: z .union([ z.number(), z.string(), z.instanceof(Decimal), z.instanceof(Prisma.Decimal), DecimalJSLikeSchema, ]) .refine((v) => isValidDecimalInput(v), { message: 'Must be a Decimal' }) .optional() .nullable(), }) .strict();
The model schema only reflects the type of the result of a database query. Therefor this type lacks all the further validation that is used in the create and update methods. So if you want to validate the input in the create and update methods you should use the input schemas instead of the model schemas or build your own custom schema using the helpers from above.
// SCHEMA //------------------------------------------------------ import { Prisma } from '@prisma/client'; export const DecimalModelSchema = z.object({ id: z.number().int(), decimal: z.instanceof(Prisma.Decimal, { message: "Field 'decimal' must be a Decimal. Location: ['Models', 'DecimalModel']", }), decimalOpt: z .instanceof(Prisma.Decimal, { message: "Field 'decimalOpt' must be a Decimal. Location: ['Models', 'DecimalModel']", }) .nullable(), }); // this schema reflects the following prisma type generated in prisma version 5.4.2: export type DecimalModel = $Result.DefaultSelection<Prisma.$DecimalModelPayload>; export type $DecimalModelPayload< ExtArgs extends $Extensions.InternalArgs = $Extensions.DefaultArgs, > = { name: 'DecimalModel'; objects: {}; scalars: $Extensions.GetPayloadResult< // this part of the type is reflected in the model schema { id: number; decimal: Prisma.Decimal; decimalOpt: Prisma.Decimal | null; }, ExtArgs['result']['decimalModel'] >; composites: {}; };
It is possible to add zod validators in the comments of the prisma.schema file with the following syntax (use rich-comments /// instead of //).
myField [prisma-scalar-type] /// @zod.[zod-type + optional[(zod-error-messages)]].[zod validators for scalar-type]This may look a bit cryptic so here is an example:
generator zod { provider = "zod-prisma-types" output = "./zod" } /// @zod.import(["import { myFunction } from 'mypackage';"]) model MyPrismaScalarsType { /// @zod.string({ invalid_type_error: "some error with special chars: some + -*#'substring[]*#!§$%&/{}[]", required_error: "some other", description: "some description" }).cuid() id String @id @default(cuid()) /// Some comment about string @zod.string.min(3, { message: "min error" }).max(10, { message: "max error" }) string String? /// @zod.custom.use(z.string().refine((val) => validator.isBIC(val), { message: 'BIC is not valid' })) bic String? /// @zod.number.lt(10, { message: "lt error" }).gt(5, { message: "gt error" }) float Float floatOpt Float? /// @zod.number.int({ message: "error" }).gt(5, { message: "gt error" }) int Int intOpt Int? decimal Decimal decimalOpt Decimal? date DateTime @default(now()) dateOpt DateTime? /// @zod.date({ invalid_type_error: "wrong date type" }) bigInt BigInt /// @zod.bigint({ invalid_type_error: "error" }) bigIntOpt BigInt? /// @zod.custom.use(z.lazy(() => InputJsonValue).refine((val) => myFunction(val), { message: 'Is not valid' })) json Json jsonOpt Json? bytes Bytes /// @zod.custom.use(z.instanceof(Buffer).refine((val) => val ? true : false, { message: 'Value is not valid' })) bytesOpt Bytes? /// @zod.custom.use(z.string().refine((val) => myFunction(val), { message: 'Is not valid' })) custom String? exclude String? /// @zod.custom.omit(["model", "input"]) updatedAt DateTime @updatedAt }
This example generates the following zod schema for the model in prisma/zod/index.ts:
import { z } from 'zod'; import * as PrismaClient from '@prisma/client'; import validator from 'validator'; import { myFunction } from 'mypackage'; export const MyPrismaScalarsTypeSchema = z.object({ id: z .string({ invalid_type_error: "some error with special chars: some + -*#'substring[]*#!§$%&/{}[]", required_error: 'some other', description: 'some description', }) .cuid(), /** * Some comment about string */ string: z .string() .min(3, { message: 'min error' }) .max(10, { message: 'max error' }) .nullish(), bic: z .string() .refine((val) => validator.isBIC(val), { message: 'BIC is not valid' }) .nullish(), float: z .number() .lt(10, { message: 'lt error' }) .gt(5, { message: 'gt error' }), floatOpt: z.number().nullish(), int: z.number().int({ message: 'error' }).gt(5, { message: 'gt error' }), intOpt: z.number().int().nullish(), decimal: z .union([ z.number(), z.string(), z.instanceof(PrismaClient.Prisma.Decimal), DecimalJSLikeSchema, ]) .refine((v) => isValidDecimalInput(v), { message: 'Field "decimal" must be a Decimal', path: ['Models', 'MyPrismaScalarsType'], }), decimalOpt: z .union([ z.number(), z.string(), z.instanceof(PrismaClient.Prisma.Decimal), DecimalJSLikeSchema, ]) .refine((v) => isValidDecimalInput(v), { message: 'Field "decimalOpt" must be a Decimal', path: ['Models', 'MyPrismaScalarsType'], }) .nullish(), date: z.coerce.date(), dateOpt: z.coerce.date({ invalid_type_error: 'wrong date type' }).nullish(), bigIntOpt: z.bigint().nullish(), json: z .lazy(() => InputJsonValue) .refine((val) => myFunction(val), { message: 'Is not valid' }), jsonOpt: NullableJsonValue.optional(), bytes: z .instanceof(Buffer) .refine((val) => (val ? true : false), { message: 'Value is not valid' }), bytesOpt: z.instanceof(Buffer).nullish(), custom: z .string() .refine((val) => myFunction(val), { message: 'Is not valid' }) .nullish(), // omitted: exclude: z.string().nullish(), updatedAt: z.date(), }); export type MyPrismaScalarsType = z.infer<typeof MyPrismaScalarsTypeSchema>; export const MyPrismaScalarsTypeOptionalDefaultsSchema = MyPrismaScalarsTypeSchema.merge( z.object({ id: z .string({ invalid_type_error: "some error with special chars: some + -*#'substring[]*#!§$%&/{}[]", required_error: 'some other', description: 'some description', }) .cuid() .optional(), date: z.date().optional(), updatedAt: z.date().optional(), }), );
Additionally all the