Skip to main content

Protection

Control which CRUD operations require authentication per entity.

Quick Options

The protected option accepts shortcuts for common patterns:

export const Post = defineEntity('Post', {
fields: { ... },
protected: 'write', // Most common: reads public, writes protected
})
Valuelistgetcreateupdateremove
false (default)publicpublicpublicpublicpublic
'write'publicpublicprotectedprotectedprotected
'all' or trueprotectedprotectedprotectedprotectedprotected

Granular Control

For fine-grained control, pass an object:

export const Comment = defineEntity('Comment', {
fields: {
content: text().required(),
},
protected: {
list: false, // Anyone can list comments
get: false, // Anyone can view a comment
create: true, // Must be logged in to create
update: true, // Must be logged in to update
remove: true, // Must be logged in to remove
},
})

Generated Router

The protection settings translate to procedure types in tRPC:

// generated/trpc/routers/comment.ts
import { publicProcedure, protectedProcedure } from '../trpc'

export const commentRouter = router({
list: publicProcedure.query(async ({ ctx }) => {
// No auth check
return db.select().from(comments)
}),

get: publicProcedure
.input(z.string())
.query(async ({ input }) => {
// No auth check
return db.select().from(comments).where(eq(comments.id, input))
}),

create: protectedProcedure
.input(commentCreateSchema)
.mutation(async ({ input, ctx }) => {
// ctx.session is guaranteed to exist
return db.insert(comments).values(input)
}),

update: protectedProcedure
.input(commentUpdateSchema)
.mutation(async ({ input, ctx }) => {
return db.update(comments).set(input.data).where(eq(comments.id, input.id))
}),

remove: protectedProcedure
.input(z.string())
.mutation(async ({ input, ctx }) => {
return db.delete(comments).where(eq(comments.id, input))
}),
})

Examples by Use Case

Blog Posts (public read, auth write)

export const Post = defineEntity('Post', {
fields: {
title: text().required(),
content: text().required(),
published: boolean().default(false),
},
protected: 'write',
})

User Profiles (fully protected)

export const UserProfile = defineEntity('UserProfile', {
fields: {
bio: text().optional(),
website: text().optional().url(),
},
protected: 'all',
})

Products (public catalog, admin write)

export const Product = defineEntity('Product', {
fields: {
name: text().required(),
price: number().required().positive(),
},
protected: 'write',
// Note: For role-based access, implement in hooks
})

Comments (custom pattern)

export const Comment = defineEntity('Comment', {
fields: {
content: text().required(),
},
protected: {
list: false, // Public timeline
get: false, // Public permalinks
create: true, // Login to comment
update: true, // Login to edit
remove: true, // Login to delete
},
})

Accessing User in Mutations

Protected procedures have access to the authenticated user via context:

// In hooks or custom logic
export const postHooks = {
async beforeCreate(input, ctx) {
// ctx.user is guaranteed for protected procedures
return {
...input,
authorId: ctx.user.id,
}
},
}

Requirements

Protection requires authentication to be enabled in your config:

export default defineConfig({
entities: [...],
auth: {
enabled: true,
providers: ['credentials'],
},
})

Without auth enabled, all entities are public regardless of the protected setting.