Comment Decorators
Prisma Guard allows you to annotate your Prisma schema files using triple-slash (///) comments to fine-tune validation constraints, define complex validation pipelines, or completely override field type definitions.
Inline Comment Decorators
You can add standard Zod validations inline directly next to your model fields.
Basic Usage
Use @zod.<validation> inline to append constraints to the inferred field:
model User {
id String @id
/// @zod.email()
email String
/// @zod.min(8).max(100)
name String
}
Multi-line Decorators
Prisma Guard supports multi-line comment blocks for complex logic, checks, and refinements. Start the block with a double-level decorator and prefix subsequent lines with /// @zod:
/// @zod.create.check(ctx => {
/// @zod if (ctx.value.role === 'ADMIN' && !ctx.value.secret) {
/// @zod ctx.issues.push({ code: 'custom', message: 'Admin needs a secret' });
/// @zod }
/// @zod })
model User {
id String @id
role String
/// @zod.optional()
secret String?
}
In the generated Zod schema, the lines starting with /// @zod are stitched together as clean, formatted JavaScript/TypeScript execution code.
Named Decorators (@zod.use)
For complex validations that are repeated across multiple models, you can define Named Decorators in your configuration. This keeps your .prisma schema readable and acts as a single source of truth for validation rules.
1. Define Decorators in Config
Define your validation pipelines inside the decorators property of prisma-guard.config.js. Use the v (validator) proxy helper:
import { defineConfig, v } from "@explita/prisma-guard";
export default defineConfig({
decorators: {
// Chain validations onto the inferred type
email: v.chain.email().trim().toLowerCase(),
// Absolute type override
strongPassword: v.string().min(12).regex(/[A-Z]/),
// Model-level validation check
ownerCheck: v.chain.check((ctx) => {
if (!ctx.value.userId) return false;
return true;
}),
},
});
2. Apply in your Prisma Schema
Reference the custom decorators using /// @zod.use(<decoratorName>):
/// @zod.use(ownerCheck)
model Project {
id String @id
/// @zod.use(email)
email String
}
Understanding the v Proxy API
Prisma Guard exports a specialized v (validator) proxy object (aliased to validator).
[!IMPORTANT] Why use
vinstead of Zod'szin your configuration?
- Proxy Serialization (Stringification): At configuration load time,
vrecords your method calls and maps them to a string representation (e.g.v.string().min(8)becomes the string"z.string().min(8)"). This enables the compiler to generate clean, native Zod code in the final schema output. Standardzexecution results in runtime objects that cannot be serialized back to typescript source code.- Unified Imports: The
vhelper acts as a single interface. It handles custom type mappings (like relations or database scalar configurations) and auto-generates external import statements for variables/constants without requiring any manual imports.
Chaining vs. Absolute Overrides
| Method | Behavior | Use Case | Example |
|---|---|---|---|
v.chain | Appends validations onto the default inferred Zod schema. | Adding validation constraints (like bounds or formats) without overriding the core database type. | v.chain.email().trim() |
Direct v call | Replaces the inferred schema definition completely. | Changing types entirely, using complex schemas, or enforcing specific runtime types. | v.string().min(10) or v.enum([...]) |
// ✅ Chain: Inferred default type (String -> z.string().trim()) is preserved, .email() is appended.
email: v.chain.email().trim()
// ✅ Override: Inferred default type is replaced with a custom enum definition.
role: v.enum(["ADMIN", "USER"])
// ❌ Warning: Chaining cannot be used to change the base type.
role: v.chain.enum(["ADMIN"]) // Will not work as expected!
v API Quick Reference
| Requirement | Syntax | Output Representation |
|---|---|---|
| Chain validations | v.chain.email().trim() | .email().trim() |
| Replace type completely | v.string().min(8) | z.string().min(8) |
| Reference constants | v.var("constants.MSG") | Imports MSG from /lib/constants.ts |
| Reference with custom import | v.var("(messages).err", "../../lib/msg") | Imports messages from ../../lib/msg |
| Reference relation schemas | v.array(v.relation("OrderItem")) | Resolves to array of circular-safe OrderItem schemas |
| Complex nested types | v.record(v.string(), v.any()) | z.object({ ... }) type structures |
Absolute Overrides in Comment Decorators
If you want to perform type overrides directly inside your .prisma schema comments rather than using a configuration decorator, use the absolute override prefix:
- Use
/// @zod.z.(e.g./// @zod.z.email().min(5)) to override the inferred type completely inline.
[!NOTE] You must use an absolute override in comments for:
- Coercion: Changing parsing behavior, e.g.
z.coerce.number().- Base Type Changes: Turning a
Stringfield into az.enum()orz.any().- Complex Structures: Defining
z.union(),z.record(), orz.lazy().
Sharing Decorators Across Projects
As your application grows, you can extract common decorators into a shared NPM or workspace package (e.g., @acme/validators) to keep validations consistent across microservices.
1. Define and Export Shared Decorators
// @acme/validators
import { v } from "@explita/prisma-guard";
export const companyDecorators = {
email: v.chain.email().trim().toLowerCase(),
taxId: v.string().regex(/^\d{2}-\d{5}$/),
phoneNumber: v.chain.regex(/^\+?[\d\s-]{10,}$/),
// Automatically imports { genders } from "../lib/constants"
gender: v.enum(v.var("constants.genders")),
};
2. Import and Register in Config
// prisma-guard.config.js
import { defineConfig } from "@explita/prisma-guard";
import { companyDecorators } from "@acme/validators";
export default defineConfig({
decorators: companyDecorators,
});
Community Decorators (Coming Soon)
As the Prisma Guard ecosystem grows, common validation presets will be shareable via public decorator packages:
@opensource/email-validators: Delivarability checks, MX lookups, or temp-mail exclusions.@opensource/id-validators: Tax numbers, VAT IDs, or local SSN validation regex mappings.