prisma-orpc-generator
v1.2.0
Published
Prisma generator that creates fully-featured oRPC routers
Maintainers
Readme
TL;DR — Add the generator to your Prisma schema and run Prisma generate. That’s it.
generator client {
provider = "prisma-client-js"
}
generator orpc {
provider = "prisma-orpc-generator"
output = "./src/generated/orpc"
// Optional config (booleans are strings)
schemaLibrary = "zod"
generateInputValidation = "true"
generateOutputValidation = "true"
// Shield authorization (optional)
generateShield = "true"
defaultReadRule = "allow"
defaultWriteRule = "auth"
}Create prisma.config.ts to hold your datasource connection (replace the URL for your database or driver adapter):
import { defineConfig } from '@prisma/config';
export default defineConfig({
schema: './schema.prisma',
datasource: {
url: 'file:./dev.db',
},
});# generate
npx prisma generate --config prisma.config.ts📋 Table of Contents
- ⚡ Quickstart - Get up and running in minutes
- 🏗️ What Gets Generated - See what files are created
- ⚙️ Configuration - All available options
- 🔧 Zod Schemas Generation - Schema validation setup
- 🛡️ Shield Authorization - Type-safe permissions and rules
- 🧪 Examples - Working examples to explore
- ❓ FAQ / Troubleshooting - Common issues and solutions
- 🧑💻 Development - Contributing guidelines
- 🗺️ Roadmap - Planned features and improvements
- 📄 License - Licensing information
- 🙏 Acknowledgements - Credits and thanks
- 📝 Changelog - Version history and changes
⚡ Quickstart
Prerequisites
- Node: 20.19.0+, 22.12.0+, or 24.0.0+ (per Prisma 7 engine support)
- Prisma CLI (v7+) in your project
- TypeScript ≥ 5.4.0 recommended
Install
# npm
npm install -D prisma-orpc-generator zod prisma @prisma/client
# pnpm
pnpm add -D prisma-orpc-generator zod prisma @prisma/client
# yarn
yarn add -D prisma-orpc-generator zod prisma @prisma/clientAdd the generator (minimal)
generator client {
provider = "prisma-client-js"
}
generator orpc {
provider = "prisma-orpc-generator"
output = "./src/generated/orpc"
}Generate
npx prisma generate --config prisma.config.ts🧩 Compatibility
🏗️ What Gets Generated
src/generated/orpc/
├─ routers/
│ ├─ models/ # per-model routers
│ └─ helpers/ # common utilities
├─ tests/ # generated tests
├─ zod-schemas/ # zod (if enabled)
└─ documentation/ # docs (if enabled)Explore the example outputs:
- Routers: examples/basic/src/generated/orpc/routers
- Zod schemas: examples/basic/src/generated/orpc/zod-schemas
- Tests: examples/basic/src/generated/orpc/tests
- Docs: examples/basic/src/generated/orpc/documentation
🛠️ Usage
Tip: Browse the example’s generated root for real structure: examples/basic/src/generated/orpc.
⚙️ Configuration
Validated against src/config/schema.ts. Below are the most commonly used options.
Core options | Option | Type | Default | Values | Description | |---|---|---|---|---| | output | string | ./src/generated/orpc | — | Directory for generated oRPC artifacts | | schemaLibrary | enum | "zod" | zod | Schema validation library | | generateInputValidation | boolean (string) | "true" | "true", "false" | Emit Zod validation for inputs | | generateOutputValidation | boolean (string) | "true" | "true", "false" | Emit Zod validation for outputs | | strictValidation | boolean (string) | "true" | "true", "false" | Stricter Zod shapes for safety | | zodSchemasOutputPath | string | ./zod-schemas | — | Relative path (under output) for Zod files | | externalZodImportPath | string | ./zod-schemas | — | Module/path used when importing Zod schemas | | zodDateTimeStrategy | enum | "coerce" | "date", "coerce", "isoString" | How DateTime fields are modeled in Zod | | zodConfigPath | string | — | — | Path to custom zod.config.json file (relative to schema or absolute) |
Operational options | Option | Type | Default | Values | Description | |---|---|---|---|---| | generateModelActions | string list | all | see note | Comma-separated actions to emit (see note below) | | showModelNameInProcedure | boolean (string) | "true" | "true", "false" | Prefix procedures with model name | | enableSoftDeletes | boolean (string) | "false" | "true", "false" | Add soft-delete semantics where applicable | | generateRelationResolvers | boolean (string) | "true" | "true", "false" | Emit helpers to resolve relations | | wrapResponses | boolean (string) | "false" | "true", "false" | Wrap handler results in an envelope |
DX and formatting | Option | Type | Default | Values | Description | |---|---|---|---|---| | useBarrelExports | boolean (string) | "true" | "true", "false" | Generate index.ts barrel exports | | codeStyle | enum | "prettier" | "prettier", "none" | Format generated code with Prettier | | generateDocumentation | boolean (string) | "false" | "true", "false" | Generate API documentation | | generateTests | boolean (string) | "false" | "true", "false" | Generate test files | | enableDebugLogging | boolean (string) | "false" | "true", "false" | Extra logs during generation |
Runtime and integration | Option | Type | Default | Values | Description | |---|---|---|---|---| | prismaClientPath | string | @prisma/client | — | Import path for PrismaClient | | contextPath | string | "" | — | Optional path to your app's Context module | | serverPort | number (string) | 3000 | — | Port used by optional docs/server helpers | | apiPrefix | string | "" | — | Prefix used by optional docs/server helpers | | apiTitle | string | Generated API | — | API title for documentation | | apiDescription | string | Auto-generated API from Prisma schema | — | API description for documentation | | apiVersion | string | 1.0.0 | — | API version for documentation |
Shield / Authorization | Option | Type | Default | Values | Description | |---|---|---|---|---| | generateShield | boolean (string) | "true" | "true", "false" | Enable shield generation | | shieldPath | string | — | — | Path to custom shield file (absolute, relative to project root, relative to output dir, or module specifier) | | defaultReadRule | enum | "allow" | "allow", "deny", "auth" | Default rule for read operations | | defaultWriteRule | enum | "auth" | "auth", "deny", "allow" | Default rule for write operations | | denyErrorCode | string | "FORBIDDEN" | — | Error code for denied access | | debug | boolean (string) | "false" | "true", "false" | Enable debug logging | | allowExternalErrors | boolean (string) | "false" | "true", "false" | Allow detailed error messages from shields | Notes
- generateModelActions supports: create, createMany, findFirst, findFirstOrThrow, findMany, findUnique, findUniqueOrThrow, update, updateMany, upsert, delete, deleteMany, aggregate, groupBy, count, findRaw, aggregateRaw.
- Booleans are strings in Prisma generator config: use "true" or "false".
- The full, authoritative shape lives in src/config/schema.ts.
generator orpc {
provider = "prisma-orpc-generator"
output = "./src/generated/orpc"
schemaLibrary = "zod"
zodDateTimeStrategy = "coerce"
generateInputValidation = "true"
generateOutputValidation = "true"
generateDocumentation = "true"
useBarrelExports = "true"
codeStyle = "prettier"
}🔧 Zod Schemas Generation
This generator leverages prisma-zod-generator to create Zod schemas from your Prisma models. Here's how the process works:
Generation Process
- Automatic Integration: When
schemaLibrary = "zod"is set, the generator automatically callsprisma-zod-generator - Configuration Management: Creates a
zod.config.jsonfile with optimized settings for oRPC usage - Schema Output: Generates Zod schemas in the
zod-schemas/subdirectory of your output path - Import Integration: Generated oRPC routers automatically import and use these schemas for validation
Configuration File
The generator creates a minimal zod.config.json file:
{
"mode": "full",
"output": "./zod-schemas"
}Additional settings are only added when they differ from defaults:
{
"mode": "full",
"output": "./zod-schemas",
"dateTimeStrategy": "date"
}DateTime Handling Strategy
The zodDateTimeStrategy option controls how Prisma DateTime fields are modeled in Zod schemas:
| Strategy | Zod Schema | Description | prisma-zod-generator equivalent |
|---|---|---|---|
| "coerce" (default) | z.coerce.date() | Automatically converts strings/numbers to Date objects | dateTimeStrategy: "coerce" |
| "date" | z.date() | Requires actual Date objects, no conversion | dateTimeStrategy: "date" |
| "isoString" | z.string().regex(ISO).transform() | Validates ISO string format, transforms to Date | dateTimeStrategy: "isoString" |
Custom Zod Configuration
For advanced use cases, you can provide your own zod.config.json:
generator orpc {
provider = "prisma-orpc-generator"
output = "./src/generated/orpc"
zodConfigPath = "./custom-zod.config.json" // Path to your config file
}When zodConfigPath is specified:
- The generator uses your existing configuration
- oRPC-specific settings are passed as generator options instead of modifying the config file
- Your custom configuration takes precedence
File Structure
Generated Zod schemas follow this structure:
src/generated/orpc/
├─ zod-schemas/
│ ├─ index.ts # Barrel exports
│ ├─ objects/ # Model schemas
│ │ ├─ UserSchema.ts
│ │ └─ PostSchema.ts
│ └─ inputTypeSchemas/ # Input validation schemas
│ ├─ UserCreateInput.ts
│ └─ UserUpdateInput.ts
└─ routers/ # oRPC routers (import from ../zod-schemas)🛡️ Shield Authorization
The generator can automatically generate orpc-shield configurations for type-safe authorization. Shield provides declarative rules, composable operators, and path-based permissions.
Shield Configuration
Add shield options to your generator config:
generator orpc {
provider = "prisma-orpc-generator"
output = "./src/generated/orpc"
// Enable shield generation
generateShield = "true"
// Option 1: Auto-generate shield rules
defaultReadRule = "allow" // "allow", "deny", "auth"
defaultWriteRule = "auth" // "auth", "deny"
// Option 2: Use custom shield file (relative to output dir)
// shieldPath = "../auth/my-custom-shield"
// Error handling
denyErrorCode = "FORBIDDEN"
debug = "false"
}What Gets Generated
Shield generation creates:
src/generated/orpc/
├─ shield.ts # Shield rules and permissions (auto-generated)
├─ routers/
│ ├─ index.ts # App router with shield exports
│ └─ helpers/
│ └─ createRouter.ts # Base router with shield middleware integrationWhen shieldPath is provided: The generator skips auto-generation and dynamically integrates your custom shield file into the generated middleware chain.
Dynamic Shield Path Resolution ✨
The generator now features smart dynamic path resolution for shield files. When you specify a shieldPath, the generator automatically:
- ✅ Resolves relative paths from your project structure
- ✅ Handles different output directory layouts
- ✅ Integrates shield middleware using the proper oRPC pattern
- ✅ Generates correct import paths regardless of nesting depth
- ✅ Applies middleware to all generated procedures through inheritance
Example Generated Integration:
// In src/generated/orpc/routers/helpers/createRouter.ts
import { permissions } from '../../../../custom-shield';
export const or = os.$context<Context>().use(permissions);Using Custom Shield Files
For advanced use cases, you can provide your own shield file instead of auto-generation:
generator orpc {
provider = "prisma-orpc-generator"
output = "./src/generated/orpc"
generateShield = "true"
shieldPath = "../../src/custom-shield" // Dynamically resolved!
}Supported Path Formats:
- Relative paths:
"../../src/auth/shield" - Project root relative:
"src/auth/shield" - Absolute paths:
"/absolute/path/to/shield"
Your custom shield file should export a permissions object:
// src/custom-shield.ts
import { rule, allow, deny, shield, or } from 'orpc-shield';
import type { Context } from '../generated/orpc/routers/helpers/createRouter';
const isAuthenticated = rule<Context>()(({ ctx }) => !!ctx.user);
const isAdmin = rule<Context>()(({ ctx }) => ctx.user?.role === 'admin');
const isOwner = rule<Context>()(({ ctx, input }) => {
return ctx.user?.id === (input as any)?.userId;
});
export const permissions = shield<Context>({
user: {
userFindMany: allow, // Match generated procedure names
userCreate: isAuthenticated,
userUpdate: isAuthenticated,
userDelete: or(isAdmin, isOwner),
userDeleteMany: deny, // Explicitly deny dangerous operations
},
post: {
postFindMany: allow,
postCreate: isAuthenticated,
postUpdate: isAuthenticated,
postDelete: isAuthenticated,
},
}, {
denyErrorCode: 'FORBIDDEN', // Maps to HTTP 403
debug: true, // Enable debug logging
allowExternalErrors: true, // Allow detailed error messages
});Important: Shield procedure names should match your generated router names (e.g., userCreate, postFindMany).
Note: When using shieldPath, the generator will skip auto-generation and use your custom shield file instead.
Generated Shield Rules
The generator creates rules based on your Prisma models:
// Built-in rules (generated automatically)
const isAuthenticated = rule<Context>()(({ ctx }) => !!ctx.user);
// Example custom rules (user-defined)
const isAdmin = rule<Context>()(({ ctx }) => ctx.user?.role === 'admin');
// Model-specific rules (generated based on config)
const canReadUser = allow; // Read operations: allow
const canWriteUser = isAuthenticated; // Write operations: require auth
// Shield configuration
export const permissions = shield<Context>({
user: {
list: canReadUser,
findById: canReadUser,
create: canWriteUser,
update: canWriteUser,
delete: canWriteUser,
},
post: {
list: allow,
create: isAuthenticated,
update: isAuthenticated,
},
});Using Shield in Your Server
Import and use the generated shield:
import { appRouter, permissions } from './generated/orpc/routers';
// Apply shield at server level
const server = createServer(appRouter, {
// Shield is applied via middleware
middleware: [permissions]
});
// Or use with oRPC handlers
import { OpenAPIHandler } from '@orpc/openapi';
const handler = new OpenAPIHandler(appRouter, {
// Shield permissions are automatically applied
interceptors: [/* your interceptors */]
});Context Requirements
Shield rules expect a Context with user information:
interface Context {
prisma: PrismaClient;
user?: {
id: string;
email?: string;
name?: string;
roles?: string[];
permissions?: string[];
};
}Customization
Override default rules by modifying the generated shield.ts:
// Custom rule for post ownership
const isPostOwner = rule<Context>()(({ ctx, input }) => {
return ctx.user?.id === (input as any)?.authorId;
});
// Use in shield config
const permissions = shield<Context>({
post: {
update: and(isAuthenticated, isPostOwner), // Auth + ownership
delete: or(isAdmin, isPostOwner), // Admin or owner
},
});Shield Options
| Option | Type | Default | Values | Description | |---|---|---|---|---| | generateShield | boolean (string) | "true" | "true", "false" | Enable shield generation | | shieldPath | string | — | — | Path to custom shield file (absolute, relative to project root, relative to output dir, or module specifier) | | defaultReadRule | enum | "allow" | "allow", "deny", "auth" | Default rule for read operations | | defaultWriteRule | enum | "auth" | "auth", "deny", "allow" | Default rule for write operations | | denyErrorCode | string | "FORBIDDEN" | — | Error code for denied access | | debug | boolean (string) | "false" | "true", "false" | Enable debug logging | | allowExternalErrors | boolean (string) | "false" | "true", "false" | Allow detailed error messages from shields |
🧪 Examples
What it does
- Builds the generator
- Generates Prisma artifacts
- Seeds a local DB
- Starts a small server using the generated routers/schemas
Notable files
- Server: examples/basic/src/server.ts
- Seed: examples/basic/src/seed.ts
- Lib utilities: examples/basic/src/lib
- Example scripts: examples/basic/package.json
❓ FAQ / Troubleshooting
Node version or ESM issues
- Symptom: runtime errors about module type or syntax
- Action: use Node 20.19.0+, 22.12.0+, or 24.0.0+; align package type with your build, then rebuild
npm run build
Generated path is unexpected
- Symptom: files not where you expect
- Action: verify your generator output path and config; compare with examples/basic/src/generated/orpc
Schema/config validation failures
- Symptom: errors referencing invalid options
- Action: check inputs against src/config/schema.ts; fix paths/booleans; re-run generation
Docs not emitted
- Symptom: documentation folder missing
- Action: set
generateDocumentation = "true"and inspect src/generators/documentation-generator.ts
Shield path resolution errors
- Symptom: "Cannot find module" errors for shield imports
- Action: verify
shieldPathpoints to correct file; check file exportspermissionsobject; ensure path is relative to project root or absolute - Note: generator now handles dynamic path resolution automatically for common directory structures
🧑💻 Development (Contributing)
Local dev loop
npm run dev # watch build
npm run build # one-off build
npm run lint # lint
npm run lint:fix # lint + fix
npm run format # prettier
npm run typecheck # types onlyLocal development (monorepo) provider example
generator orpc {
provider = "../../lib/bin.js" // relative path to built generator
output = "./src/generated/orpc"
}Testing
npm test # unit/integration
npm run test:watch # watch
npm run test:e2e # Prisma-backed CRUD
npm run test:coverage # coverageConventions
- Conventional Commits
- Ensure
npm run buildandnpm run typecheckpass before PR - Update README.md if flags/outputs change
🗺️ Roadmap
- ✅ Integration with oRPC Shield - Built-in authorization with dynamic path resolution
- Schema-based Auth Configuration - Define authorization rules directly in Prisma schema, JSON, or TypeScript config files
- Plugin hooks for custom emitters
- Config discovery and overrides
📄 License
- License: MIT — see the
LICENSEfile. - Copyright © 2025 Omar Dulaimi.
🙏 Acknowledgements
- Prisma and its ecosystem
- oRPC community and patterns
- Zod for runtime validation
- TypeScript tooling
- Vitest and contributors
📝 Changelog
See CHANGELOG.md
Made with ❤️ by Omar Dulaimi
