prisma-zodify
v0.1.0
Published
Prisma generator that produces accurate, type-safe Zod schemas for your models, enums, and input types.
Maintainers
Readme
prisma-zodify
A Prisma generator that produces accurate, type-safe Zod schemas from your Prisma models, enums, and input types.
- Zero runtime overhead — generated at build time, not runtime
- Parity with Prisma — relations, optional fields, defaults,
@updatedAt, lists, enums,Decimal,Json,Bytes,BigInt - Create & Update inputs — opt-in
*CreateInputSchemaand*UpdateInputSchemafor every model - Dual package — ships ESM + CJS
- Works with Zod 3.23+ and Zod 4 — no vendor lock-in
- Small & focused — one dependency (
@prisma/generator-helper)
Installation
npm install --save-dev prisma-zodify
# or
pnpm add -D prisma-zodify
# or
yarn add -D prisma-zodifyprisma-zodify has peer dependencies on prisma (>=5) and zod (>=3.23); install both in your project if you haven't already.
Usage
Add the generator to your schema.prisma:
generator zod {
provider = "prisma-zodify"
output = "./generated/zod"
}Run Prisma:
npx prisma generateImport the schemas anywhere in your project:
import { UserSchema, UserCreateInputSchema, RoleSchema } from './generated/zod';
const user = UserSchema.parse(await prisma.user.findFirstOrThrow());
const input = UserCreateInputSchema.parse({
email: '[email protected]',
name: 'Ada Lovelace',
});Example
Given this Prisma schema:
generator zod {
provider = "prisma-zodify"
output = "./generated/zod"
}
enum Role {
USER
ADMIN
}
model User {
id String @id @default(cuid())
email String @unique
name String?
role Role @default(USER)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
posts Post[]
}
model Post {
id String @id @default(cuid())
title String
views Int @default(0)
authorId String
author User @relation(fields: [authorId], references: [id])
}prisma-zodify generates:
// generated/zod/index.ts
import { z } from 'zod';
// ============================================================================
// Enums
// ============================================================================
export const RoleSchema = z.enum(['USER', 'ADMIN']);
export type Role = z.infer<typeof RoleSchema>;
// ============================================================================
// Models
// ============================================================================
export const UserSchema = z.object({
id: z.string(),
email: z.string(),
name: z.string().nullable(),
role: RoleSchema,
createdAt: z.coerce.date(),
updatedAt: z.coerce.date(),
});
export type User = z.infer<typeof UserSchema>;
export const PostSchema = z.object({
id: z.string(),
title: z.string(),
views: z.number().int(),
authorId: z.string(),
});
export type Post = z.infer<typeof PostSchema>;
// ============================================================================
// Input Types
// ============================================================================
export const UserCreateInputSchema = z.object({
id: z.string().optional(),
email: z.string(),
name: z.string().nullable().optional(),
role: RoleSchema.optional(),
createdAt: z.coerce.date().optional(),
});
export type UserCreateInput = z.infer<typeof UserCreateInputSchema>;
export const UserUpdateInputSchema = z.object({
email: z.string().optional(),
name: z.string().nullable().optional(),
role: RoleSchema.optional(),
createdAt: z.coerce.date().optional(),
});
export type UserUpdateInput = z.infer<typeof UserUpdateInputSchema>;
// …and the same for PostConfiguration
All options go inside the generator block in schema.prisma. Every value is a string (Prisma's generator config format).
| Option | Type | Default | Description |
| ------------------- | ------------------------------- | ------------------- | ------------------------------------------------------------------------ |
| output | string | ./generated/zod | Directory for generated files. |
| useDateCoercion | "true" | "false" | "true" | Use z.coerce.date() instead of z.date() for DateTime. |
| generateInputs | "true" | "false" | "true" | Emit *CreateInputSchema and *UpdateInputSchema for each model. |
| emitInferredTypes | "true" | "false" | "true" | Emit export type User = z.infer<typeof UserSchema> alongside schemas. |
| importPath | string | "zod" | Module the generated file imports Zod from. |
| prefix | string | "" | Prepended to every generated schema identifier. |
| suffix | string | "Schema" | Appended to every generated schema identifier. |
| decimalFormat | "string" \| "number" \| "union" | "union" | How Decimal is represented in Zod. union → z.union([z.number(), z.string()]). |
Full example
generator zod {
provider = "prisma-zodify"
output = "./src/schemas"
useDateCoercion = "true"
generateInputs = "true"
emitInferredTypes = "true"
importPath = "zod"
prefix = ""
suffix = "Schema"
decimalFormat = "string"
}Type mapping
| Prisma | Zod (default) |
| ----------- | ------------------------------------------ |
| String | z.string() |
| Boolean | z.boolean() |
| Int | z.number().int() |
| BigInt | z.bigint() |
| Float | z.number() |
| Decimal | z.union([z.number(), z.string()]) |
| DateTime | z.coerce.date() |
| Json | z.unknown() |
| Bytes | z.instanceof(Uint8Array) |
| T? | .nullable() |
| T[] | z.array(T) |
| enum E | z.enum([...]) |
| @relation | skipped in base schemas — use the FK column instead |
Create vs Update inputs
- Create — required fields stay required. Fields with
@default(...),@id @default(...), or that are nullable become.optional(). Fields with@updatedAtare skipped. - Update — every scalar and enum field becomes
.optional().@idand@updatedAtare skipped.
This matches how Prisma Client handles them in the common case without having to traverse the full input-type graph from the DMMF schema.
Recipes
Validating API request bodies
import { UserCreateInputSchema } from './generated/zod';
app.post('/users', async (req, res) => {
const parsed = UserCreateInputSchema.safeParse(req.body);
if (!parsed.success) return res.status(400).json(parsed.error.flatten());
const user = await prisma.user.create({ data: parsed.data });
res.json(user);
});Validating responses
import { UserSchema } from './generated/zod';
const user = UserSchema.parse(await prisma.user.findUniqueOrThrow({ where: { id } }));Extending generated schemas
import { UserCreateInputSchema } from './generated/zod';
export const SignupSchema = UserCreateInputSchema.extend({
password: z.string().min(12),
confirmPassword: z.string(),
}).refine((v) => v.password === v.confirmPassword, {
message: 'Passwords must match',
path: ['confirmPassword'],
});Programmatic API
You can invoke the generator directly — useful for tests or custom pipelines:
import { generateModel, generateEnum } from 'prisma-zodify';
const code = generateModel(model, {
output: '/tmp',
useDateCoercion: true,
generateInputs: true,
emitInferredTypes: true,
importPath: 'zod',
prefix: '',
suffix: 'Schema',
decimalFormat: 'union',
});Development
git clone https://github.com/filstawski/prisma-zodify.git
cd prisma-zodify
npm install
npm run build
npm testTo try the generator against the bundled example:
npm run build
npx prisma generate --schema ./examples/basic/schema.prismaVersioning
prisma-zodify follows Semantic Versioning:
- Patch — bug fixes, doc updates
- Minor — new options, new generator outputs (backwards-compatible)
- Major — breaking changes to generated output or config surface
The peer ranges on prisma and zod mean you're free to upgrade those independently; a major-version bump of prisma-zodify is only required when the generator itself breaks.
License
MIT © Filip Podstawski
