Skip to main content

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

DecoratorApplied 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();