Permix — getting started
Docs: https://permix.letstri.dev/docs/quick-start
Upgrading from v3? Use action tuples, not { action, dataType } — https://permix.letstri.dev/docs/migration-v3-to-v4
Install
pnpm add permix
# or npm install permix / yarn add permix
1. Define the permission schema (once)
Create a shared module (e.g. lib/permix.ts). The generic on createPermix<D>() is the source of truth for all paths and rules.
Nested resources (most common):
import { createPermix } from 'permix'
export const permix = createPermix<{
post: ['create', 'read', 'update', 'delete']
comment: ['create', 'read']
}>()
Flat list (single resource):
export const permix = createPermix<['read', 'write']>()
Deep tree (orgs, workspaces, etc.):
export const permix = createPermix<{
workspace: {
billing: ['view', 'update']
member: ['invite', 'remove']
}
}>()
Every action you declare in D must appear in every setup() call (use false to deny).
2. Assign rules with setup
permix.setup({
post: {
create: true,
read: true,
update: false,
delete: false,
},
comment: {
create: true,
read: true,
},
})
- Static leaf:
boolean - Depends on resource at check time:
(data) => boolean(see permix skill,references/check.md) - Call
setupafter login, on route change, or when the active user/tenant changes — it replaces previous rules.
3. Optional: initial rules at construction
Skip a separate bootstrap step when rules are known upfront:
export const permix = createPermix<{
post: ['read']
}>({
post: { read: true },
})
permix.isReady() // true immediately
4. Reusable role presets — template
const admin = permix.template({
post: { create: true, read: true, update: true, delete: true },
})
const member = permix.template({
post: { create: false, read: true, update: true, delete: false },
})
// After resolving the user's role:
permix.setup(admin())
Dynamic template (parameters):
const forUser = permix.template((user: User) => ({
post: {
update: (post) => post.authorId === user.id,
},
}))
permix.setup(forUser(currentUser))
5. Typed rules factory — createRules
Use when rules live in another file but must stay type-safe:
import { createPermix, createRules } from 'permix'
const rules = createRules<{
post: ['create']
}>({
post: { create: true },
})
permix.setup(rules)
6. First check
permix.check('post.read') // boolean
Before setup, check throws PermixNotReadyError. Unknown paths throw PermixRuleNotDefinedError.
7. React to permission changes (optional)
permix.hook('setup', () => {
// re-run when rules change (e.g. refresh UI cache)
})
permix.hookOnce('ready', () => {
// first successful setup only
})
Docs: https://permix.letstri.dev/docs/guide/events
Checklist for new apps
- Single exported
permixinstance (same reference everywhere) - Schema covers every permission the app uses
-
setupruns when auth/session is known - UI waits for
isReadyor handles not-ready (see permix skill,references/frontend.md) - Server routes use middleware (see permix skill,
references/server.md) — never rely on client checks alone