permxv0.4

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.

step 01

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

step 02

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.

permissions.ts
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)
step 03

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.

server.ts
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],
    },
  ],
})
step 04

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.

routes.ts
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)
step 05

Gate the UI

Wrap React components with headless gates. Same permission keys on both sides of the wire — the backend and frontend always agree.

App.tsx
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