Get started with PermX.
From zero to a working RBAC system in three steps. Define permissions with typed keys, wire middleware on the backend, and gate UI components on the frontend — all sharing the same permission vocabulary.
Install
Add the core engine and optionally the React SDK. PermX has zero runtime dependencies — mongoose and express are optional peers.
backend
$bun add @permx/core
frontend
$bun add @permx/react
with mongoose + express
$bun add @permx/core mongoose express
Define permissions
Declare your permission schema once with definePermissions(). TypeScript infers literal key strings — every rename propagates to middleware and UI gates at compile time.
import { definePermissions } from '@permx/core'
export const P = definePermissions({
projectsView: {
module: 'projects',
resource: 'tasks',
action: 'view',
scope: 'all',
},
projectsEdit: {
module: 'projects',
resource: 'tasks',
action: 'update',
scope: 'own',
},
viewSalary: {
module: 'people',
resource: 'employees',
action: 'view',
scope: 'own',
field: 'salary',
},
} as const)Create the engine
Pass your data provider — Mongoose, Prisma, or a custom PermXDataProvider implementation. The engine returns the authorize function used by both server middleware and client SDK.
import { createPermX } from '@permx/core/mongoose'
import mongoose from 'mongoose'
const permx = createPermX({
connection: mongoose.connection,
cache: { ttl: 300_000 },
})
// Seed on first run
await permx.syncFromConfig({
modules: [{ key: 'projects', name: 'Projects' }],
permissions: [
{
key: P.projectsView,
name: 'View Projects',
moduleKey: 'projects',
},
],
roles: [
{
key: 'editor',
name: 'Editor',
permissionKeys: [P.projectsView],
},
],
})Protect routes
Wrap your Express (or Hono, Fastify, Koa) routes with the middleware. The engine resolves role inheritance, checks the permission key, and returns 403 on denial.
import { createPermXMiddleware } from '@permx/core/express'
import { P } from './permissions'
const auth = createPermXMiddleware(permx, {
extractUserId: (req) => req.user?.id,
extractTenantId: (req) => req.headers['x-tenant-id'],
})
app.get('/projects', auth.authorize(P.projectsView), listProjects)
app.put('/projects/:id', auth.authorize(P.projectsEdit), updateProject)Gate the UI
Wrap React components with headless gates. Same permission keys on both sides of the wire — the backend and frontend always agree.
import { PermXProvider, Can, CanField, RouteGuard } from '@permx/react'
function App() {
return (
<PermXProvider fetchPermissions={fetchUserPermissions}>
<Can componentId="edit-project-btn">
<EditButton />
</Can>
<CanField fieldId="salary">
<SalaryColumn />
</CanField>
<RouteGuard routeId="/admin" fallback={<NoAccess />}>
<AdminPanel />
</RouteGuard>
</PermXProvider>
)
}Next steps
- Browse the source — full API in the README
- Examples directory — Express, Hono, Next.js App Router, React demo
- Compare with CASL, Casbin, Permit.io — spec sheet on the homepage