Virtual Fields & Refinements
Prisma Guard allows you to add virtual properties (fields that do not exist in your database but are required for request validation) and perform model-level schema checks (such as checking if password fields match).
Custom Virtual Fields (@zod.add)
Virtual fields are defined at the Model level using the /// @zod.add comment decorator. This is ideal for UI validations such as password confirmation fields or terms-of-service agreements.
Option 1: Inline Definitions
You can write raw Zod code inline directly inside your comment block:
/// @zod.add confirmPassword: z.string().min(8)
/// @zod.add acceptTerms: z.boolean().refine(val => val === true, "You must accept the terms")
model User {
id Int @id @default(autoincrement())
email String @unique
password String
}
Option 2: Shorthand Preset Lookup (@zod.add.use)
If you have already defined a decorator pattern in your prisma-guard.config.js file, you can load it as a virtual field using the /// @zod.add.use(<decoratorName>) shorthand. The field name in the output schema will automatically match the decorator name.
/// @zod.add.use(confirmPassword)
model User {
id Int @id @default(autoincrement())
password String
}
Model-Level Refinements (@zod.refine / /// @zod.create.refine)
You can apply Zod refinements or checks onto the entire model object to cross-validate multiple fields.
Refinement Scope Reference
| Decorator | Applied To |
|---|---|
/// @zod.refine(...) | Both Create & Update schemas. |
/// @zod.create.refine(...) | Only the main Create schema. |
/// @zod.update.refine(...) | Only the Partial Update schema. |
/// @zod.use(name) | Shared preset decorator at model level. |
/// @zod.create.use(name) | Create-only preset decorator at model level. |
/// @zod.update.use(name) | Update-only preset decorator at model level. |
Real-World Example: Password Matching
Here is how to combine @zod.add virtual fields and model-level refinements to enforce matching password inputs during registration:
Prisma Schema:
/// @zod.add confirmPassword: z.string()
/// @zod.create.refine(data => data.password === data.confirmPassword, {
/// @zod message: "Passwords do not match",
/// @zod path: ["confirmPassword"]
/// @zod })
model Account {
id String @id @default(uuid())
email String @unique
password String
}
Generated Zod Output:
// generated/zod/account.ts
import { z } from "zod";
import { REQUIRED_MESSAGE } from "../lib/constants";
export const AccountCreateSchema = z
.object({
id: z.string().trim(),
email: z.string().trim().min(1, { message: REQUIRED_MESSAGE }),
password: z.string().trim().min(1, { message: REQUIRED_MESSAGE }),
confirmPassword: z.string(),
})
.refine(
(data) => data.password === data.confirmPassword,
{
message: "Passwords do not match",
path: ["confirmPassword"],
}
);
export const AccountUpdateSchema = AccountCreateSchema.partial();