🛡️ Automatically generate tRPC Shield permissions from your Prisma schema
A powerful Prisma generator that creates tRPC Shield configurations from your Prisma schema. Automatically generates type-safe permission rules for all your database operations, saving you time and reducing boilerplate code.
If this tool helps you build better applications, please consider supporting its development:
Your sponsorship helps maintain and improve this project. Thank you! 🙏
Now with full Prisma 6 & tRPC 11 support!
npm install prisma-trpc-shield-generator
This release includes major upgrades to the latest Prisma 6+ and tRPC v11+ - bringing compatibility with the latest versions and their breaking changes. Report any issues to help us continue improving!
- Features
- Quick Start
- Generated Output
- Configuration Options
- Advanced Usage
- Examples
- Troubleshooting
- Contributing
- License
- Related Projects
- 🚀 Zero Configuration - Works out of the box with sensible defaults
- 🔄 Auto-Generated - Updates every time you run
prisma generate
- 🛡️ Type Safe - Full TypeScript support with proper typing
- 🎯 Comprehensive - Covers all Prisma operations (queries, mutations, aggregations)
- ⚙️ Configurable - Customize output directory and context path
- 📦 Lightweight - Minimal dependencies and fast generation
npm install prisma-trpc-shield-generator trpc-shield
- Add the generator to your
schema.prisma
:
generator trpc_shield {
provider = "prisma-trpc-shield-generator"
contextPath = "../src/context"
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
author User? @relation(fields: [authorId], references: [id])
authorId Int?
}
- Generate your shield:
npx prisma generate
- Use the generated permissions:
import { permissions } from './generated/shield';
import { t } from './trpc';
export const permissionsMiddleware = t.middleware(permissions);
export const protectedProcedure = t.procedure.use(permissionsMiddleware);
The generator creates a comprehensive shield configuration with all CRUD operations:
import { shield, allow } from 'trpc-shield';
import { Context } from '../../../context';
export const permissions = shield<Context>({
query: {
// Find operations
findUniqueUser: allow,
findFirstUser: allow,
findManyUser: allow,
findUniquePost: allow,
findFirstPost: allow,
findManyPost: allow,
// Aggregation operations
aggregateUser: allow,
aggregatePost: allow,
groupByUser: allow,
groupByPost: allow,
},
mutation: {
// Create operations
createOneUser: allow,
createOnePost: allow,
// Update operations
updateOneUser: allow,
updateOnePost: allow,
updateManyUser: allow,
updateManyPost: allow,
// Delete operations
deleteOneUser: allow,
deleteOnePost: allow,
deleteManyUser: allow,
deleteManyPost: allow,
// Upsert operations
upsertOneUser: allow,
upsertOnePost: allow,
},
});
Option | Description | Type | Default |
---|---|---|---|
output |
Output directory for the generated shield | string |
./generated |
contextPath |
Path to your tRPC context file (relative to output) | string |
../../../../src/context |
generator trpc_shield {
provider = "prisma-trpc-shield-generator"
output = "./src/shields"
contextPath = "../context"
}
Replace the default allow
rules with your custom logic:
import { permissions } from './generated/shield';
import { rule, and, or } from 'trpc-shield';
const isAuthenticated = rule()(async (parent, args, ctx) => {
return ctx.user !== null;
});
const isOwner = rule()(async (parent, args, ctx) => {
const post = await ctx.prisma.post.findUnique({
where: { id: args.where.id },
select: { authorId: true }
});
return post?.authorId === ctx.user?.id;
});
// Override specific permissions
export const customPermissions = {
...permissions,
mutation: {
...permissions.mutation,
createOnePost: and(isAuthenticated),
updateOnePost: and(isAuthenticated, isOwner),
deleteOnePost: and(isAuthenticated, isOwner),
}
};
import { initTRPC } from '@trpc/server';
import { customPermissions } from './shields/permissions';
const t = initTRPC.context<Context>().create();
export const permissionsMiddleware = t.middleware(customPermissions);
export const protectedProcedure = t.procedure.use(permissionsMiddleware);
export const appRouter = t.router({
user: t.router({
create: protectedProcedure
.input(z.object({ name: z.string(), email: z.string() }))
.mutation(({ input, ctx }) => {
return ctx.prisma.user.create({ data: input });
}),
}),
});
import { rule, and } from 'trpc-shield';
const isAuthenticated = rule()(async (parent, args, ctx) => {
return !!ctx.user;
});
const canManagePosts = rule()(async (parent, args, ctx) => {
if (!ctx.user) return false;
// Admin can manage all posts
if (ctx.user.role === 'ADMIN') return true;
// Users can only manage their own posts
if (args.where?.authorId) {
return args.where.authorId === ctx.user.id;
}
return false;
});
export const permissions = shield<Context>({
query: {
findManyPost: allow, // Public read access
findUniquePost: allow,
findManyUser: isAuthenticated, // Authenticated read access
},
mutation: {
createOnePost: isAuthenticated,
updateOnePost: and(isAuthenticated, canManagePosts),
deleteOnePost: and(isAuthenticated, canManagePosts),
},
});
Error: Cannot find module '../context'
- Ensure your
contextPath
is correct relative to the output directory - Check that your context file exports a
Context
type
TypeScript errors in generated shield
- Make sure
trpc-shield
is installed and up to date - Verify your tRPC context is properly typed
Shield not updating after schema changes
- Run
npx prisma generate
after modifying your schema - Check that the generator is properly configured in
schema.prisma
Contributions are welcome! Please read our Contributing Guide for details.
This project is licensed under the MIT License - see the LICENSE file for details.
- tRPC Shield - The permission system this generator creates
- Prisma - The database toolkit this integrates with
- tRPC - The TypeScript RPC framework this works with
Made with ❤️ by Omar Dulaimi