Skip to content

Permissions

A simple RBAC (Role-Based Access Control) system with a twist: roles live in the database, while permissions are defined in config. This hybrid approach keeps role assignments flexible (database) while keeping permission definitions simple and version-controlled (config).

How it works

User

Roles (DB)

Permissions (Config)

  • Users are assigned roles (stored in user_roles database table)
  • Roles map to permissions (defined in packages/config/index.ts)

Define roles and their permissions in packages/config/index.ts:

const config = {
permissions: {
roleToPermissions: {
premium: {
viewPremiumContent: true,
},
elite: {
viewPremiumContent: true,
viewEliteContent: true,
},
},
},
}

All functions are exported from @/permissions.

FunctionPurpose
requireUserPermissions({ data: { userId, permissions } })Server function - throws if user lacks any of the specified permissions
requireUserRole({ data: { userId, roleName } })Server function - throws if user doesn’t have the role
hasUserRole(userId, roleName)Returns boolean - use when you need to check without throwing
grantUserRole(userId, roleName)Assigns a role to user
revokeUserRole(userId, roleName)Removes a role from user

Use requireUserPermissions in beforeLoad to restrict access by permission:

import { createFileRoute } from "@tanstack/react-router"
import { requireUserId } from "@/auth"
import { requireUserPermissions } from "@/permissions"
export const Route = createFileRoute("/premium")({
beforeLoad: async () => {
const userId = await requireUserId()
await requireUserPermissions({ data: { userId, permissions: ["viewPremiumContent"] } })
// User has permission, continue...
},
})

Or requireUserRole to restrict access by role:

import { createFileRoute } from "@tanstack/react-router"
import { requireUserId } from "@/auth"
import { requireUserRole } from "@/permissions"
export const Route = createFileRoute("/elite")({
beforeLoad: async () => {
const userId = await requireUserId()
await requireUserRole({ data: { userId, roleName: "elite" } })
// User has role, continue...
},
})

For conditional checks without throwing:

import { hasUserRole } from "@/permissions"
const isElite = await hasUserRole(userId, "elite")

Assign or remove roles from users:

import { grantUserRole, revokeUserRole } from "@/permissions"
// After a purchase
await grantUserRole(userId, "premium")
// When subscription ends
await revokeUserRole(userId, "premium")