@explita/prisma-guard
v0.1.1
Published
The ultimate Prisma companion for input sanitization and Zod schema generation. Protect your database from unknown fields and validate your data with ease.
Maintainers
Readme
Prisma Guard
The ultimate Prisma companion for input sanitization and professional Zod schema generation. Protect your database from unknown fields and validate your data with zero-effort, type-safe schemas.
📋 Table of Contents
- 🚀 Why Prisma Guard?
- ✌️ Two Ways to Use Prisma Guard
- 📦 Installation
- 🛠️ Configuration
- 📖 Features & Usage
- 🪄 IDE Integration
- 🛡️ Prisma Extension
- 🚀 Production & CI/CD
- 🏗️ Architecture
- 🔧 Troubleshooting
- 💖 Support the Mission
- 📜 License
🚀 Why Prisma Guard?
Prisma is great, but validating inputs and stripping unknown fields can be a manual chore. Prisma Guard bridges this gap by providing:
- 🛡️ Runtime Protection: A Prisma extension that silently strips unknown fields from your queries.
- ⚡ Zod Generation: Automatically transforms your Prisma models into robust, decorated Zod schemas.
- 🧙 IDE Superpowers: Automated VS Code snippets and metadata for a seamless developer experience.
- ✨ Zero-Config: Automatic
.gitignoremanagement, Prettier formatting, and folder cleanup. - 📋 CLI Command Reference: A comprehensive CLI command reference with detailed information about each command and its flags, with quick start examples.
✌️ Two Ways to Use Prisma Guard
Mode 1: Runtime Protection Only
- Add the extension to your Prisma client
- No Zod schemas generated
- Fields silently stripped
- Best for: Quick protection, no validation needs
Mode 2: Full Validation (Recommended)
- Generate Zod schemas
- Validate before database operations
- Provide clear error messages to users
- Best for: Production APIs, user input validation
📦 Installation
npm install @explita/prisma-guard⚡ Quick Start (2 minutes)
# 1. Create your config
npx prisma-guard init
# 2. Create Prisma Schema files
# Place these files in the schema directory (default: ./prisma)
model User {
id String @id @default(cuid())
email String @unique
password String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
# 3. Generate schemas
npx prisma-guard
# 4. Use in your code
import { PrismaClient } from "@prisma/client";
import { prismaGuard } from "@explita/prisma-guard";
const prisma = new PrismaClient().$extends(prismaGuard());
// That's it! Extra fields are automatically stripped
🛠️ Configuration
You can use a .json file, or a .js / .mjs file for full type safety and logic.
Example: prisma-guard.config.(js|mjs|json)
Option A: .js or .mjs with defineConfig (Recommended)
This provides full IntelliSense and type checking:
import { defineConfig } from "@explita/prisma-guard";
export default defineConfig({
schemaDir: "./prisma",
outputDir: "./generated",
omitIds: true,
omitDates: true, // Automatically skip createdAt/updatedAt
autoTrim: true, // Automatically add .trim() to all strings (default: true)
schemaSuffix: "Schema", // Suffix for generated schemas (default: "Schema")
enumSuffix: "Enum", // Suffix for generated enums (default: "Enum")
useJsDoc: false, // Use /** */ instead of .describe()
typeMap: {
DateTime: "z.string().datetime()",
},
zodOmit: ["password", "secret", "resetToken"], // Global omission
defaultsOnOverride: false, // Keep @default() when using @zod.z.override
// Without this (default: false):
// /// @zod.z.enum(["A","B"]) → z.enum(["A","B"]) (default LOST)
// With this (true):
// /// @zod.z.enum(["A","B"]) → z.enum(["A","B"]).default("A") (default KEPT)
});Option B: .js or .mjs with JSDoc
If you don't want to import the helper:
/** @type {import('@explita/prisma-guard').PrismaGuardConfig} */
export default {
schemaDir: "./prisma",
// IntelliSense works here too!
};Option C: .json
{
"schemaDir": "./prisma",
"outputDir": "./generated",
"omitIds": true,
"omitDates": true,
"autoTrim": true,
"schemaSuffix": "Schema",
"enumSuffix": "Enum",
"useJsDoc": false,
"generateZod": true,
"debug": false,
"typeMap": {
"DateTime": "z.string().datetime()"
}
}Configuration Notes:
schemaDir: Optional. Defaults to"./prisma". Path to the directory containing your Prisma schema files.outputDir: Optional. Defaults tonode_modules/.prisma-guard.- If you only use the Prisma Guard extension, you can skip this.
- If you need Zod schemas in your code, set this to a committed directory (e.g.,
./src/generated).
omitIds: Optional. Defaults tofalse. Remove all@idfields from generated Zod schemas.omitDates: Optional. Defaults tofalse. Remove timestamp fields (e.g.,createdAtandupdatedAt) from generated schemas.generateZod: Optional. Defaults totrue. Whether to generate Zod schemas. Iffalse, only the runtime guard utilities are generated.zodOmit: Optional. Defaults to[]. Global list of field names to omit from ALL generated Zod schemas (e.g.,["password", "secret"]).autoTrim: Optional. Defaults totrue. Automatically add.trim()to allz.string()validations.schemaSuffix: Optional. Defaults to"Schema". Suffix appended to generated Zod schema names (e.g.,UserSchema).enumSuffix: Optional. Defaults to"Enum". Suffix appended to generated Zod enum schemas (e.g.,RoleEnum).useJsDoc: Optional. Defaults tofalse. Use/** */comments in generated TypeScript files instead of.describe().debug: Optional. Defaults tofalse. Enable debug logging to console.prettier: Optional. Defaults totrue. Set tofalseto disable automatic Prettier formatting on generated files.skipGitignore: Optional. Defaults tofalse. Set totrueto prevent the tool from automatically updating your.gitignore.typeMap: Optional. You only need to provide the types you want to override.decorators: Optional. Named validation decorators for reuse across your Prisma schema with/// @zod.use(name).fullScalar: Optional. Defaults totrue. When enabled, the generator creates additional Scalar-focused Zod schemas and TypeScript types (e.g.ServiceScalar,ServiceCreateRequired) that are highly ergonomic for service-layer inputs.defaultsOnOverride: Optional. Defaults tofalse. By default, using an absolute override (///@zod.z.oroverride) strips Prisma's@default()from the Zod schema. Set totrueto force appending.default()even on overridden fields.
✨ Zero-Config Magic: Prisma Guard automatically manages your
.gitignore, formats generated code with Prettier, and cleans up old folders. No manual setup required.
Custom Type Mappings:
| Prisma Type | Default Zod Mapping |
| :---------- | :---------------------------------- |
| String | z.string().trim() |
| Int | z.number() |
| BigInt | z.string().trim() |
| Float | z.number() |
| Decimal | z.union([z.number(), z.string()]) |
| Boolean | z.boolean() |
| DateTime | z.coerce.date() |
| Json | z.unknown() |
| Bytes | z.instanceof(Buffer) |
📖 Features & Usage
Smart Multi-File Grouping
Prisma Guard respects your organization. If you use multiple .prisma files (e.g., via Prisma's prismaSchemaFolder feature), the generator will mirror that structure.
schema/user.prisma→generated/zod/user.tsschema/product.prisma→generated/zod/product.ts
This keeps your schemas clean and prevents massive, unmanageable files.
Zod Schema Generation
Transform your .prisma files into kebab-cased .ts files containing Zod schemas for every model.
npx prisma-guard generate
# Or just generate the Zod schemas
npx prisma-guard zod💎 Advanced Type Ergonomics
Prisma Guard doesn't just generate schemas; it provides a complete set of TypeScript types for your entire application lifecycle.
By default (via fullScalar: true), every model generates Dual Schemas:
- Public Schemas (
UserCreateSchema): Respects yourzodOmitand@zod.omitrules. Perfect for API validation. - Scalar Schemas (
UserCreateScalarSchema): Includes every database field. Perfect for internal services and DB operations.
Generated Types Reference
For a model named Service, the following types are exported:
| Type | Description |
| :-------------------------- | :---------------------------------------------------------------------------------------------------------------- |
| ServiceCreate | The output of ServiceCreateSchema. Restricted for public APIs. |
| ServiceInput | The raw input for ServiceCreateSchema (before Zod defaults). |
| ServiceUpdate | Partial version of ServiceCreate for patch operations. |
| ServiceScalar | The internal database record (includes omitted fields). |
| ServiceScalarInput | Raw input for the scalar schema. |
| ServiceScalarUpdate | Partial version of the internal record. |
| ServiceCreateRequired | The ultimate service type. Omits all fields with defaults (id, createdAt, etc.) but requires internal fields. |
[!TIP] Internal Service Layer: Always use
ModelCreateRequiredfor your service functions. It ensures you provide required internal fields (liketenantId) while keeping DB-managed fields (likeidorstatus) optional!
Multi-line Comment Decorators
Use triple-slash (///) comments to fine-tune your schemas. We support complex, multi-line logic for refinements and checks.
Prisma Schema:
/// @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?
}Generated Zod:
// generated/zod/user.ts
import { z } from "zod";
import { REQUIRED_MESSAGE } from "../lib/constants";
export const UserSchema = z
.object({
id: z.string().trim().min(1, { message: REQUIRED_MESSAGE }),
role: z.string().trim().min(1, { message: REQUIRED_MESSAGE }),
secret: z.string().trim().nullish().optional(),
})
.check((ctx) => {
if (ctx.value.role === "ADMIN" && !ctx.value.secret) {
ctx.issues.push({ code: "custom", message: "Admin needs a secret" });
}
});Named Decorators (@zod.use)
For complex validation logic that you use frequently, you can define "Named Decorators" in your prisma-guard.config.js. This keeps your Prisma schema clean and centralizes your validation logic.
1. Define Decorators in Config
// prisma-guard.config.js
import { defineConfig, pg, z } from "@explita/prisma-guard";
export default defineConfig({
decorators: {
// A reusable chain (using 'pg' for chainable parts)
email: pg.email().trim().toLowerCase(),
// A complete override (using 'z' for full schemas)
strongPassword: z.string().min(12).regex(/[A-Z]/),
// A complex model check (with full IntelliSense!)
ownerCheck: pg.check((ctx) => {
if (!ctx.value.userId) return false;
return true;
}),
},
});[!TIP] Autocomplete & IntelliSense: Always import
zandpgfrom@explita/prisma-guardinstead ofzod. This provides full autocomplete for your decorators while allowing the generator to correctly "stringify" your validation logic into the generated files.
2. Use in Prisma Schema
/// @zod.use(ownerCheck)
model Project {
id String @id
/// @zod.use(email)
email String
}Generated Zod:
// generated/zod/project.ts
import { z } from "zod";
import { REQUIRED_MESSAGE } from "../lib/constants";
export const ProjectSchema = z
.object({
id: z.string().trim().min(1, { message: REQUIRED_MESSAGE }),
email: z.string().trim().email().trim().toLowerCase(),
})
.check((ctx) => {
if (!ctx.value.userId) return false;
return true;
});🛠️ Persistence & Customization
Prisma Guard generates a lib/constants.ts file in your output directory.
- Persistence: Unlike the
zod/andguards/folders (which are reset every time), thelib/folder is persistent. - Version Control: Our
metadata --vscodecommand automatically addszod/andguards/to your.gitignore, but it leaveslib/alone. You should commit thelib/folder to your repository so your team shares the same validation constants. - Custom Messages: You can change the
REQUIRED_MESSAGEinlib/constants.tsto whatever you like. - The
ref()Helper: Reference external variables and optionally handle imports automatically.
| Syntax | Behavior | Generated Output |
| :----------------------- | :-------------------------------------- | :--------------------------------------- |
| ref("constants.MSG") | Auto-import from lib/constants.ts | import { MSG } from "../lib/constants" |
| ref("(constants).VAL") | Ignore (No auto-import) | constants.VAL (assumes manual import) |
| ref("myVar") | Raw Reference (No auto-import) | myVar (assumes manual import) |
- Ignored References: Wrap a part in parentheses to suppress the auto-import (e.g.,
ref("(constants).genders")). This is useful if you've already imported the variable manually using///@zod.import.
// prisma-guard.config.js
import { ref, z } from "@explita/prisma-guard";
export default {
decorators: {
// This will auto-import { EMAIL_MESSAGE } from "../lib/constants"
// and generate: .email(EMAIL_MESSAGE)
email: z.string().email(ref("constants.EMAIL_MESSAGE")),
// This will NOT auto-import anything (useful if you have manual imports)
// and generate: .enum(constants.genders)
gender: z.enum(ref("(constants).genders")),
},
};Real-World Example
Here's how everything works together:
// In lib/constants.ts (persistent, user-editable)
export const REQUIRED_MESSAGE = "This field is required";
export const EMAIL_MESSAGE = "Please enter a valid email address";
// In config (decorators)
email: z.string().email(ref("constants.EMAIL_MESSAGE"));
// Generated output
import { EMAIL_MESSAGE } from "../lib/constants";
email: z.string()
.trim()
.min(1, { message: REQUIRED_MESSAGE })
.email(EMAIL_MESSAGE);📦 Sharing Decorators Across Projects!
[!TIP] As your team grows, you can extract common decorators into a shared npm package (e.g.,
@acmecorp/prisma-validators). Import them in your config and reuse across all your services!
Create a shared decorator library:
// @mycompany/prisma-decorators
import { pg, z, ref } from "@explita/prisma-guard";
export const companyDecorators = {
email: pg.email().trim().toLowerCase(),
taxId: z.string().regex(/^\d{2}-\d{5}$/),
phoneNumber: pg.regex(/^\+?[\d\s-]{10,}$/),
// Automatically imports { genders } from "../lib/constants"
gender: z.enum(ref("constants.genders")),
};
// In your project:
import { defineConfig } from "@explita/prisma-guard";
import { companyDecorators } from "@mycompany/prisma-decorators";
export default defineConfig({
decorators: companyDecorators,
});📖 Understanding pg, z, and ref:
- `pg` (Prisma Guard) - Use for **chainable methods** (e.g., `.email()`, `.trim()`)
- `z` (Zod) - Use for **complete overrides** (e.g., `z.string().email()`)
- `ref` (Reference) - Use for **external variables** (e.g., `ref("constants").genders`)
All provide full IntelliSense,
but `pg` methods are safer for default types since they preserve the base type's behavior.Model-Level Refinements
Apply validation logic to the entire model, and even target specific operations (create vs update).
| Decorator | Applied To |
| :---------------------------- | :----------------------------- |
| /// @zod.refine(...) | Both Create & Update schemas |
| /// @zod.create.refine(...) | Only the main Schema |
| /// @zod.update.refine(...) | Only the Partial Update Schema |
Custom Virtual Fields (@zod.add)
Add fields to the Zod schema that do not exist in the database. Perfect for confirmPassword or terms checkboxes.
This must be added at the Model level.
/// @zod.add confirmPassword: z.string().min(8)
/// @zod.add terms: z.boolean().refine(v => v === true, "Must accept terms")
model User {
id Int @id
password String
}Generated Output:
export const UserSchema = z.object({
id: z.number(),
password: z.string().trim().min(1, { message: REQUIRED_MESSAGE }),
confirmPassword: z.string().min(8),
terms: z.boolean().refine((v) => v === true, "Must accept terms"),
});Handling Arrays
While Prisma Guard automatically wraps list fields in z.array(), you can use overrides for more complex array validation (like minimum length).
Option A: Inline Override
model Post {
/// @zod.z.array(z.string()).min(1)
tags String[]
}Option B: Named Decorator (Recommended)
// prisma-guard.config.js
decorators: {
tags: "z.array(z.string()).min(1).max(5)",
}model Post {
/// @zod.use(tags)
tags String[]
}Omission & Customization
- Model Omission: Add
/// @zod.omitat the top of a model to skip generating its file. - Field Omission: Add
/// @zod.omitto a field to remove it from the schema. - Global Omission: Add
zodOmit: ["password", "secret"]to your config to omit fields globally. @zod.include: Override global omission for a specific field (e.g.,/// @zod.include).@zod.add: Add custom fields (e.g.,/// @zod.add confirmPassword: z.string()).@zod.override: Bypass default mapping (e.g.,/// @zod.override z.string().uuid()).@zod.z.: Shorthand for overrides (e.g.,/// @zod.z.email().min(5)).@zod.import: Add custom imports to the top of the file.
[!IMPORTANT] Absolute Overrides vs. Decorators:
- Use Decorators (e.g.,
/// @zod.min(5)) to append logic to the inferred Prisma type.- Use Absolute Overrides (e.g.,
/// @zod.z.) to replace the inferred type entirely.You must use an absolute override for:
- Coercion:
z.coerce.number()(because.coerceis not a method on schema instances).- Type Changes: Changing a
Stringfield toz.enum()orz.any().- Complex Structures: Using
z.union(),z.record(), orz.lazy().
Watch Mode (Developer Experience)
Tired of manually running the generator every time you tweak your Prisma schema? Use the built-in watcher to automatically re-generate your Zod schemas and guards whenever a .prisma file is saved.
npx prisma-guard --watch
# or
npx prisma-guard -wThe watcher is highly optimized with a built-in debounce, ensuring it only triggers once even if your IDE performs multiple rapid saves. It's designed to stay out of your way while keeping your codebase perfectly in sync with your database models.
🪄 IDE Integration (The Magic)
Get full autocompletion for @zod decorators inside your .prisma files.
npx prisma-guard metadata --vscodeThis command does three things:
- Generates Snippets: Creates
prisma.code-snippetswith every Zod method (.email(),.min(), etc.). - Installs Snippets: Places them in your
.vscode/folder. - Protects Your Repo: Automatically adds the
generated/folders to your.gitignore.
🛡️ Prisma Extension (The Guard)
Add the extension to your Prisma client to enable automatic field stripping at runtime.
import { PrismaClient } from "@prisma/client";
import { prismaGuard } from "@explita/prisma-guard";
const prisma = new PrismaClient().$extends(prismaGuard());
// Any extra fields passed to 'data' will be silently stripped
await prisma.user.create({
data: {
email: "[email protected]",
poisonField: "will be removed",
},
});⚠️ Understanding the Guard's Boundaries
Prisma Guard strips from:
datain create/update operations- Nested
create/update/upsertin relations
Prisma Guard does NOT strip from:
whereclauses (would break queries)select/include(output filtering is your responsibility)orderBy,groupBy,havingclauses
Prisma still validates:
- Field name typos (throws error)
- Missing required fields (throws error)
- Type mismatches (throws error)
Think of Prisma Guard as a filter, not a validator.
// ✅ Extra field - stripped
await prisma.user.create({
data: { email: "[email protected]", oldField: "deprecated" },
}); // Works fine
// ❌ Typo - Prisma throws
await prisma.user.create({
data: { emial: "[email protected]" },
}); // Error: Unknown argument `emial`
// ❌ Missing required - Prisma throws
await prisma.user.create({
data: { name: "John" }, // email is required
}); // Error: Missing required argument `email`🚀 Production & CI/CD
Since the zod/ and guards/ folders are ignored by Git, you must generate them during your build process (on your server or CI/CD).
Update your package.json scripts:
{
"scripts": {
"prisma:guard": "npx prisma-guard --no-prettier --skip-gitignore",
"build": "pnpm prisma:guard && next build",
"// or for general TS projects": "pnpm prisma:guard && tsc"
}
}Note: If your config file does not contain
generateZod: true, make sure to add the--zodflag to theprisma:guardcommand so your schemas are generated for production!
CLI Command Reference
| Command | Description |
| :----------------------------------- | :---------------------------------------------------- |
| npx prisma-guard init | Creates a default prisma-guard.config.js file |
| npx prisma-guard | Runs both field generation and Zod schema generation. |
| npx prisma-guard zod | Generates only the Zod schemas. |
| npx prisma-guard metadata --vscode | Generates metadata and VS Code snippets. |
| npx prisma-guard --watch or -w | Watch mode - auto-regenerate on schema changes |
Common Flags:
--dry-run: See what would be generated without writing to disk.--vscode: Automatically install snippets and update.gitignore(used withmetadata).--schema-dir: Override the Prisma schema directory.--output-dir: Override the output directory.--omit-ids: Force omit@idfields from generated Zod schemas.--omit-dates: Force omit date fields (createdAt, updatedAt) from schemas.--zodor--generate-zod: Force Zod generation even if disabled in config.--skip-gitignore: Skip automatic.gitignoreupdates.--no-prettier: Disable automatic Prettier formatting.--prettier: Force Prettier formatting even if disabled in config.--helpor-h: Show the help information.--watchor-w: Watch for changes in your Prisma schema files
🏗️ Architecture
- Kebab-Case Output:
UserModelbecomesuser.ts. - Base Object Pattern: To prevent duplication, we define a private base object and build
SchemaandUpdateSchemafrom it. - Prettier Support: Automatically formats generated files using your local Prettier config.
⚡ Performance
The runtime guard uses a highly optimized recursive sanitizer with:
- **O(n)** complexity where n = number of fields
- **Memoized** field whitelists per model
- **Early exit** on non-object inputs
- **Depth limiting** (max 10 levels) to prevent stack overflow
Benchmarks: ~0.3ms overhead per query on average.🔧 Troubleshooting
"Cannot find module '../generated/zod/user'"
Run npx prisma-guard to generate schemas.
VS Code snippets not working
Reload VS Code window (Cmd/Ctrl + Shift + P → "Developer: Reload Window")
My custom decorators aren't being applied
Check that decorators is in your config and you're using /// @zod.use(name) where name is the name of the decorator in your config.
My @default values disappeared from Zod schemas!
If you're using /// @zod.z. overrides and your Prisma @default() isn't showing up:
Fix: Set defaultsOnOverride: true in your config.
Why: By default, explicit overrides assume you want full control, including default values. Set to true to automatically append .default() to your overrides.
Prisma is yelling about missing required fields!
The Scenario: You generated your Prisma Guard field mappings, later added a new required column to your Prisma schema, but forgot to regenerate. Prisma Guard's runtime stripper did exactly what it was designed to do—it didn't recognize the new field, stripped it from your input, and then Prisma yelled at you because a required field was missing!
The Fix: Always remember to run npx prisma-guard after modifying your schema to update your mappings, or use npx prisma-guard --watch during development to avoid this completely!
Pro Tip: If you're ever unsure why data isn't saving or why Prisma is complaining, temporarily set
debug: truein yourprisma-guard.config.js. You'll get helpful console logs like:[prisma-guard] Stripping extra field "newColumn" from model "User"!
🌟 Community Decorators (Coming Soon)
As the Prisma Guard community grows, we expect to see shared decorator packages emerge for common validation patterns:
@company/validators- Internal company validation rules@opensource/email-validators- Email format and deliverability checks@opensource/id-validators- Tax IDs, SSNs, VAT numbers, etc.
Have a useful decorator collection? Let us know and we'll feature it here!
💖 Support the Mission
Prisma Guard is built to save developers time and prevent costly database errors. If it has helped you, please consider supporting the project to ensure its continued growth and maintenance!
🚀 Ways to Contribute
- Give us a ⭐: It helps others discover the project.
- Join the Discussion: Report bugs or suggest new features.
- Spread the Word: Share your experience with Prisma Guard on social media.
🙏 Our Amazing Supporters
A huge thank you to everyone helping us build the future of type-safe database guards!
📜 License
MIT © Explita
