Zod
The @zenstackhq/zod package generates Zod validation schemas from your ZModel definitions. It provides type-safe schemas for models, embedded types, and enums, with full support for validation attributes and custom validation rules.
Installation​
- npm
- pnpm
- bun
- yarn
npm install @zenstackhq/zod zod
pnpm add @zenstackhq/zod zod
bun add @zenstackhq/zod zod
yarn add @zenstackhq/zod zod
Zod 4.0 or above is required.
API​
The package exports a createSchemaFactory function that takes a ZenStack schema as input and returns a factory for creating Zod schemas.
import schema from './zenstack/schema';
import { createSchemaFactory } from '@zenstackhq/zod';
const factory = createSchemaFactory(schema);
The factory exposes the following methods:
-
makeModelSchemaCreates a schema for the full shape of a model. By default, all scalar fields are included, and all relation fields are included as optional fields.
Available since v3.5.0You can pass an optional second argument with
select,include, oromitoptions to control which fields and relations are included in the resulting schema. These options work recursively for relation fields, mirroring the ORM's query API semantics.select— pick only the listed fields. Set a field totrueto include it with its default shape, or pass a nested options object for relation fields. Mutually exclusive withincludeandomit.include— start with all scalar fields, then additionally include the named relation fields. Can be combined withomit.omit— remove named scalar fields from the default set. Can be combined withinclude, but mutually exclusive withselect.
// Select specific fields only
const schema = factory.makeModelSchema('User', {
select: { id: true, email: true },
});
// Include a relation with nested selection
const schema = factory.makeModelSchema('User', {
select: {
id: true,
email: true,
posts: {
select: { id: true, title: true },
},
},
});
// Include relations on top of all scalar fields
const schema = factory.makeModelSchema('User', {
include: { posts: true },
});
// Omit specific scalar fields
const schema = factory.makeModelSchema('User', {
omit: { password: true },
});
// Combine include and omit
const schema = factory.makeModelSchema('User', {
include: { posts: true },
omit: { password: true },
});The resulting Zod schema is fully typed — the inferred TypeScript type reflects exactly which fields are present based on the options you provide.
-
makeModelCreateSchemaCreates a schema for creating new records, with fields that have defaults being optional. The result schema excludes relation fields.
-
makeModelUpdateSchemaCreates a schema for updating records, with all fields being optional. The result schema excludes relation fields.
-
makeTypeSchemaCreates a schema for a Custom Type.
-
makeEnumSchemaCreates a schema for an enum type.
Schema Features​
The created Zod schemas have the following features:
- They are strongly typed.
- They verify the basic shapes of input args (object fields and types).
- They respect the additional validation attributes like
@email,@length, etc., as described in Input Validation. - If a ZModel declaration has a
@@metaor@metaattribute with "description" key, the meta value will be used as the Zod schema's metadata.
Samples​
// This is a sample model to get you started.
datasource db {
provider = 'sqlite'
}
/// User role
enum Role {
USER
ADMIN
}
/// Profile type definition
type Profile {
bio String?
website String? @url
}
/// User model
model User {
id Int @id @default(autoincrement())
email String @unique @email @meta('description', 'The unique email address of the user')
role Role @default(USER)
profile Profile? @json
posts Post[]
@@meta('description', 'A registered user of the application')
}
/// Post model
model Post {
id Int @id @default(autoincrement())
title String
author User? @relation(fields: [authorId], references: [id])
authorId Int?
}
import { createSchemaFactory } from '@zenstackhq/zod';
import { schema } from './zenstack/schema';
const factory = createSchemaFactory(schema);
// enum
const roleSchema = factory.makeEnumSchema('Role');
console.log('"STAFF" is a valid Role?', roleSchema.safeParse('STAFF').success);
// type
const profileSchema = factory.makeTypeSchema('Profile');
console.log(
'Profile allows invalid website?',
profileSchema.safeParse({ website: 'not-a-url' }).success,
);
// User model's create schema
const userCreateSchema = factory.makeModelCreateSchema('User');
console.log(
'User create schema allows omitting "role"?',
userCreateSchema.safeParse({ email: 'alice@example.com' }).success,
);
// User model full schema
const userSchema = factory.makeModelSchema('User');
console.log(
'User full model allows relations?',
userSchema.safeParse({
id: 1,
email: 'user@example.com',
role: 'ADMIN',
profile: { bio: 'A developer', website: 'https://example.com' },
posts: [{ id: 1, title: 'Post1' }],
}).success,
);
// `@@meta` and `@meta` handling
console.log(
'User schema has description meta:',
userSchema.meta()?.description,
);
console.log(
'User.email field has description:',
userCreateSchema.shape.email.meta()?.description,
);