# Comparison
URL: https://aminzoubaa.github.io/permix/docs/comparison
Comparison with other libraries
## Overview
Permix is a library that provides a way to manage permissions in your application. It is designed to be used with React, Vue, etc. But not only Permix can manage permissions, there are other libraries that can do the same thing.
## Comparison
| Feature | Permix | CASL |
| ------------ | --------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| Type-safe | ✅ Native | ✅ Via [external type](https://casl.js.org/v6/en/advanced/typescript#permissions-inference) |
| Saving rules | ✅ Via [template](/docs/guide/template) | ✅ Via [AppAbility](https://casl.js.org/v6/en/cookbook/cache-rules#the-issue) |
| Hydration | ✅ Native | ⚠️ Via custom implementation |
| Entity | ✅ Depends on props of a [passed object](/docs/guide/setup#type-based) | ⚠️ Class-based yes, but object-based via [external function](https://casl.js.org/v6/en/guide/subject-type-detection) |
| Events | ✅ | ❌ |
| Simple DX | ✅ Create instance, use built-in integrations | ❌ In CASL you need to manage a lot of stuff manually (type-safe, hydration, etc.) |
| Modernity | ✅ Uses modern updates and features of each lib and framework | ❌ CASL was created a long time ago and hasn't updated the core |
| Size | \~1.6kb gzip core bundle, \~18kb published tarball | \~21kb (ability) + \~2.5kb (framework adapter) |
# Introduction
URL: https://aminzoubaa.github.io/permix/docs
The type-safe permission management you've always needed
## Idea
In my many years of experience, I have worked extensively with permissions management, and early in my career I wrote solutions that looked like this:
```ts
if (user.role === 'admin') {
// do something
}
```
Later, I started using [CASL](https://casl.js.org) for permissions management in a [Vue](https://vuejs.org/) application.
```ts
can('read', ['Post', 'Comment']);
can('manage', 'Post', { author: 'me' });
can('create', 'Comment');
```
But time goes on, CASL becomes older, and developers' needs grow, especially for type-safe libraries. Unfortunately, CASL couldn't satisfy my type validation needs and so I started thinking again about writing my own validation solution. But this time I wanted to make it as a library, as I already had experience with open-source.
## Implementation
I started to create my own solution. However, nothing occurred to me until I watched a Web Dev Simplified [video](https://www.youtube.com/watch?v=5GG-VUvruzE) where he demonstrated an example of implementing permission management as he envisioned it. I really liked his approach because it was based on type-safety, which is exactly what I needed.
So I'm ready to present to you my permission management solution called Permix!
## DX
When creating Permix, the goal was to simplify DX as much as possible without losing type-safety and provide the necessary functionality.
That is why you only need to write the following code to get started:
```ts twoslash
import { createPermix } from '@aminzoubaa/permix'
const permix = createPermix<{
post: {
action: 'read'
}
}>()
permix.setup({
post: {
read: true,
}
})
const canReadPost = permix.check('post', 'read') // true
```
It looks too simple, so here's a more interesting example:
```ts twoslash
import type { PermixDefinition } from '@aminzoubaa/permix'
import { createPermix } from '@aminzoubaa/permix'
// You can take types from your database
interface User {
id: string
role: 'editor' | 'user'
}
interface Post {
id: string
title: string
authorId: string
published: boolean
}
interface Comment {
id: string
content: string
authorId: string
}
// Create definition to describe your permissions
type PermissionsDefinition = PermixDefinition<{
post: {
dataType: Post
action: 'create' | 'read' | 'update' | 'delete'
}
comment: {
dataType: Comment
action: 'create' | 'read' | 'update'
}
}>
const permix = createPermix()
// Define permissions for different users
const editorPermissions = permix.template({
post: {
create: true,
read: true,
update: post => !post?.published,
delete: post => !post?.published,
},
comment: {
create: false,
read: true,
update: false,
},
})
const userPermissions = permix.template(({ id: userId }: User) => ({
post: {
create: false,
read: true,
update: false,
delete: false,
},
comment: {
create: true,
read: true,
update: comment => comment?.authorId === userId,
},
}))
async function getUser() {
// Imagine that this function is fetching user from database
return {
id: '1',
role: 'editor' as const,
}
}
// Setup permissions for signed in user
async function setupPermix() {
const user = await getUser()
const permissionsMap = {
editor: () => editorPermissions(),
user: () => userPermissions(user),
}
permix.setup(permissionsMap[user.role]())
}
// Call setupPermix where you need to setup permissions
setupPermix()
// Check if a user has permission to do something
const canCreatePost = permix.check('post', 'create')
async function getComment() {
// Imagine that this function is fetching comment from database
return {
id: '1',
content: 'Hello, world!',
authorId: '1',
}
}
const comment = await getComment()
const canUpdateComment = permix.check('comment', 'update', comment)
```
## Benefits
What are the benefits of using Permix?
* 100% type-safe without writing TypeScript (except for initialization)
* Single source of truth for your entire app
* Perfect match for TypeScript monorepos
* Zero dependencies
* Useful methods for specific cases
* Large number of integrations for different frameworks, such as [React](/docs/integrations/react), [Vue](/docs/integrations/vue), [Express](/docs/integrations/express), and more.
## What Changed In v4
Permix `4.0.0` focuses on correctness in SSR and modern React frameworks:
* Next.js now has an explicit request-scoped integration path through [`permix/next`](/docs/integrations/next).
* Multiple roles can be merged into one effective rules object through [`mergeRules()`](/docs/guide/setup).
* Hydration distinguishes `false` from "not serializable yet" through `null`, which removes hidden false negatives for function rules.
* Client state can be invalidated explicitly when logout, org switching, or server events make the current snapshot stale.
## Pick A Path
Learn the smallest possible Permix setup from scratch.
Read the supported App Router architecture, cache rules, server/client boundaries, and troubleshooting advice.
Start with the smallest official App Router example.
Explore a full multi-role demo with nested routes and a local database.
Review the major-version changes, breaking types, and migration steps.
## Ready?
Ready to take Permix to your project? Let's go to the [Quick Start](/docs/quick-start) page.
# Quick Start
URL: https://aminzoubaa.github.io/permix/docs/quick-start
A quick start guide to start using Permix and validating your permissions
## Try Permix
Want to explore Permix before installing? Try our interactive sandbox environment where you can experiment with type-safe permissions management right in your browser.
[Try Permix Sandbox](https://stackblitz.com/edit/permix-sandbox?file=src%2Fmain.ts\&terminal=dev)
## Installation
### Install Permix
Typically you'll need to install Permix using your package manager:
npm
pnpm
yarn
bun
```bash
npm install @aminzoubaa/permix
```
```bash
pnpm add @aminzoubaa/permix
```
```bash
yarn add @aminzoubaa/permix
```
```bash
bun add @aminzoubaa/permix
```
### Create an instance
To create a base instance, you need to provide a schema as a generic type to `createPermix` function that defines your permissions:
```ts title="/lib/permix.ts"
import { createPermix } from '@aminzoubaa/permix'
export const permix = createPermix<{
post: {
action: 'create' | 'read' | 'update' | 'delete'
}
}>()
// ...
```
Learn more about features and configuration of instances in the [instance guide](/docs/guide/instance).
### Setup your permissions
You can setup your permissions by calling `setup` method on your instance in any place you want:
```ts title="/lib/permix.ts"
// ...
// Call setupPermissions in your application
export function setupPermissions() {
permix.setup({
post: {
create: true,
read: true,
update: true,
delete: false,
},
})
}
```
### Check permissions
After setup, you can use `check` method to check available permissions:
```ts
permix.check('post', 'create') // true
```
### Finish
That's it! 🎉
You've now got a basic setup of Permix. The next step is to learn more about 3 core concepts of Permix:
* [Instance](/docs/guide/instance)
* [Setup](/docs/guide/setup)
* [Check](/docs/guide/check)
* Of course, do not hesitate to read other guide pages.
## Integrations
Continuing from the quick start, you can now explore how Permix integrates with other libraries and frameworks.
Integration with React via provider and hook.
App Router guidance for snapshots, caching, invalidation, Hono, and tRPC.
Integration with Vue via plugin and composable.
Integration with Node.js via middleware.
Integration with Hono via middleware.
Integration with Express via middleware.
Integration with tRPC via middleware.
## Reference Apps
If you prefer learning from working projects, these are the official Next.js reference apps shipped in the repository.
The smallest supported App Router integration with a protected page, API route, and client UI guard.
Focused red-to-green repro cases for caching, hydration, layout guards, Hono, and tRPC.
A larger multi-role demo with SQLite, impersonation, nested routes, and protected APIs.
# Enum-based Example
URL: https://aminzoubaa.github.io/permix/docs/examples/enum-based
A React example that uses enums instead of string literals for permission actions
## Purpose
`examples/enum-based` shows how to use enums as the action vocabulary for Permix. This is useful when your codebase already models permissions as centralized constants and you want autocomplete plus refactor safety from those enums.
## What it demonstrates
* `enum` values as permission actions
* setup through a single static ruleset
* React hook usage plus direct `permix.check(...)`
* declarative checks with the `Check` component
## Core idea
The example moves action names into enums such as `PostPermission.Create`, then uses those enum members everywhere instead of plain strings.
```ts title="lib/permissions.ts"
export enum PostPermission {
Create = 'create',
Read = 'read',
Update = 'update',
Delete = 'delete',
}
```
That same enum member is then used in the UI and in imperative checks:
```tsx title="App.tsx"
if (!permix.check('post', PostPermission.Create)) {
return
}
```
## Run it
```bash
cd examples/enum-based
pnpm dev
```
## Key files
* `src/lib/permissions.ts`: enum declarations for each entity
* `src/lib/permix.ts`: typed Permix instance using enum-backed actions
* `src/App.tsx`: component usage and imperative checks
## When to use it
Use this shape if your team wants permissions to live in shared enums instead of freeform strings. It keeps large codebases more uniform and makes renaming actions less error-prone.
## Source code
[Browse the example on GitHub](https://github.com/AminZoubaa/permix/tree/main/examples/enum-based)
# Express + tRPC + React Example
URL: https://aminzoubaa.github.io/permix/docs/examples/express-trpc-react
A split client/server example with shared rules, tRPC middleware, and React UI checks
## Purpose
`examples/express-trpc-react` demonstrates a fuller stack: Express hosts the tRPC server, React renders the client, and both sides share the same typed permission vocabulary.
## What it demonstrates
* shared permission definitions in one module
* server-side permission setup inside a tRPC procedure middleware
* `permix.checkMiddleware(...)` for tRPC procedure protection
* client-side React checks against the same role model
## Core idea
The shared rules module defines role-specific permissions once:
```ts title="shared/permix.ts"
export function getRules(role: 'admin' | 'user') {
const rolesMap = {
admin: adminPermissions,
user: userPermissions,
}
return rolesMap[role]
}
```
The server attaches those rules to procedure context:
```ts title="server/main.ts"
ctx: {
permix: permix.setup(getRules(user.role)),
},
```
And the client uses the same model for UI gating before making requests.
## Run it
```bash
cd examples/express-trpc-react
pnpm dev:server
```
In a second terminal:
```bash
cd examples/express-trpc-react
pnpm dev:client
```
## Key files
* `shared/permix.ts`: shared permission definition and role rules
* `server/main.ts`: Express+tRPC server and protected procedures
* `client/src/permix.ts`: client-side Permix instance
* `client/src/App.tsx`: UI checks and tRPC calls
## When to use it
Use this example when you want one typed permission contract shared between API middleware and a separate frontend.
## Source code
[Browse the example on GitHub](https://github.com/AminZoubaa/permix/tree/main/examples/express-trpc-react)
# Express Example
URL: https://aminzoubaa.github.io/permix/docs/examples/express
A small Express server showing middleware-based permission setup and route protection
## Purpose
`examples/express` is the smallest server-side adapter example in the repository. It demonstrates how to attach Permix to Express, provide request rules, and protect routes with middleware.
## What it demonstrates
* `createPermix()` from `@aminzoubaa/permix/express`
* request-scoped setup through `setupMiddleware(...)`
* route protection with `checkMiddleware(...)`
* imperative access to the request instance through `permix.get(req, res)`
## Core idea
Rules are attached to every request before the router runs:
```ts title="main.ts"
app.use(permix.setupMiddleware(() => ({
user: {
read: true,
write: false,
},
})))
```
Routes can then opt into the required permission:
```ts title="main.ts"
router.get('/', permix.checkMiddleware('user', 'read'), (req, res) => {
res.send('Hello World')
})
```
## Run it
```bash
cd examples/express
pnpm start
```
The server listens on `http://localhost:3000`.
## Key files
* `main.ts`: Express app, adapter setup, and route protection
## When to use it
Use this example when you want a server-first integration reference without React, hydration, or client state.
## Source code
[Browse the example on GitHub](https://github.com/AminZoubaa/permix/tree/main/examples/express)
# Feature Flags Example
URL: https://aminzoubaa.github.io/permix/docs/examples/feature-flags
A React example that models feature access as typed permissions
## Purpose
`examples/feature-flags` shows that Permix is not limited to classic CRUD authorization. You can also model feature access, experiments, and progressive rollouts as typed permission checks.
## What it demonstrates
* feature flags represented as entities and actions
* early-return UI gating for hidden features
* imperative checks before calling protected client logic
* a tiny ruleset that reads like product rollout configuration
## Core idea
The example treats flags such as `newUI` or `experimentalAPI` as permissions:
```ts title="lib/permix.ts"
betaFeatures: {
newUI: true,
experimentalAPI: false,
}
```
That lets the component hide the whole new UI until the flag is enabled:
```tsx title="App.tsx"
if (!check('betaFeatures', 'newUI')) {
return null
}
```
## Run it
```bash
cd examples/feature-flags
pnpm dev
```
## Key files
* `src/lib/permix.ts`: feature-flag rules and setup helper
* `src/hooks/use-permissions.ts`: React wrapper around `usePermix`
* `src/App.tsx`: gated UI and guarded client-side action
## When to use it
Use this pattern when the question is "should this user see or use this feature?" rather than "can this user mutate this record?".
## Source code
[Browse the example on GitHub](https://github.com/AminZoubaa/permix/tree/main/examples/feature-flags)
# Next App Router Repros
URL: https://aminzoubaa.github.io/permix/docs/examples/next-app-router-repros
Red-to-green reproduction cases for Next.js caching, hydration, layout guards, and framework handlers
## Purpose
`examples/next-app-router` is the focused repro harness used to prove the original failure cases and lock them down with end-to-end tests.
## Covered scenarios
* cached permission snapshots after role changes
* hydration readiness after SSR
* multi-role rule composition
* overlapping request isolation
* server layout guards with `notFound()`
* Hono route handlers in the App Router
* tRPC route handlers in the App Router
## Run it
```bash
pnpm --filter next-app-router dev
```
Run the end-to-end tests:
```bash
pnpm --filter next-app-router test:e2e
```
## Why this example matters
This example is intentionally narrower than the CMS demo. It exists so a bug in caching, hydration, or request isolation can be reproduced in a very small surface area before it gets lost inside a larger app.
## Suggested use
* Start here when you suspect a framework integration bug.
* Use `next-minimal` when you need a teaching example.
* Use `next-blog-cms` when you want to explore a realistic multi-role application.
# Next Blog CMS Demo
URL: https://aminzoubaa.github.io/permix/docs/examples/next-blog-cms
A larger multi-role Next.js demo with local data, impersonation, nested routes, and API guards
## Purpose
`examples/next-blog-cms` is the broad demo application for Permix in Next.js. It simulates a small editorial CMS with nested App Router workspaces and different permission combinations.
It demonstrates:
* multiple concurrent roles merged into one effective ruleset
* server-guarded pages, layouts, and APIs
* nested routes for post overview, comments, review, and publish flows
* adaptive navigation and per-role workspace panels
* impersonation through a debug panel
* local SQLite-backed demo data
## Run it
```bash
pnpm --filter next-blog-cms dev
```
Run the end-to-end tests:
```bash
pnpm --filter next-blog-cms test:e2e
```
## Roles in this demo
* `author`: can work on authored content
* `reviewer`: can review queued content
* `editor`: can coordinate editorial work
* `support`: can access moderation-focused surfaces
* `admin`: can publish and access every protected area
Some seeded users intentionally combine roles so the merged behavior is visible.
## Demo features
* a local SQLite database created under `.data/blog-cms.sqlite`
* seeded posts, comments, users, and session state
* a debug panel for impersonation and version visibility
* protected publish API returning realistic JSON responses
* role-aware sidebar navigation
The demo uses Tailwind and Flowbite styling, but its build is pinned to the stable `webpack` path because the current Flowbite/PostCSS/Turbopack combination was not stable enough for a reproducible demo in this repository.
## Key files
* `lib/db.ts`: local demo database and seeded session state
* `lib/permissions.ts`: merged role rules
* `components/debug-panel.tsx`: impersonation controls and session diagnostics
* `app/posts/[slug]/layout.tsx`: nested workspace guard
* `app/api/posts/[slug]/publish/route.ts`: protected API mutation
## Why this demo exists
This example is not just for marketing. It is a regression harness for real App Router behavior where role changes, nested layouts, and server/client boundaries can drift apart if the integration is not explicit enough.
# Next Minimal Example
URL: https://aminzoubaa.github.io/permix/docs/examples/next-minimal
The smallest supported Next.js App Router integration for Permix
## Purpose
`examples/next-minimal` is the smallest official App Router example in this repository. It exists to show the supported baseline without extra framework noise.
It demonstrates:
* a protected server page with `notFound()`
* a protected API route
* a client-side UI guard
* a per-mount client Permix instance
* a tiny role switcher so behavior is easy to verify
## Run it
```bash
pnpm --filter next-minimal dev
```
Run the end-to-end tests:
```bash
pnpm --filter next-minimal test:e2e
```
## Roles in this example
* `viewer`: can read the shell but cannot enter the editor workspace
* `editor`: can access the editor page and editor API
* `admin`: included as a simple higher-privilege role for extension
## Why start here
Start with this example if you want to understand the supported shape before adding caching, multiple roles, Hono, tRPC, or more complex layout guards.
## Key files
* `app/page.tsx`: current session overview
* `app/editor/page.tsx`: server-guarded page
* `app/api/editor-data/route.ts`: server-guarded API route
* `components/client-permissions.tsx`: client-side UI gating
* `lib/server-permix.ts`: request-scoped server snapshot creation
## What this example intentionally does not include
* no external auth provider
* no database
* no framework adapter
* no function-based rules
That keeps the example small enough for onboarding and debugging.
# React Example
URL: https://aminzoubaa.github.io/permix/docs/examples/react
A React example with object-aware checks and reactive UI updates
## Purpose
`examples/react` is the main non-Next React example. It demonstrates data-aware permissions where the answer depends on the current record, not just on the action name.
## What it demonstrates
* React integration with `PermixProvider` and `usePermix`
* permission predicates that inspect the checked object
* asynchronous setup after the current user becomes available
* `Check` component usage next to imperative `check(...)`
## Core idea
The edit permission depends on the current post:
```ts title="lib/permix.ts"
edit: post => post?.authorId === user.id,
```
That lets the same user edit some posts but not others, while keeping the check strongly typed.
## Run it
```bash
cd examples/react
pnpm dev
```
## Key files
* `src/lib/permix.ts`: typed React Permix instance and `Check` component
* `src/hooks/permissions.ts`: local hook wrapper
* `src/App.tsx`: per-post checks and conditional rendering
## When to use it
Use this example when you need object-aware React checks outside Next.js. If you are using App Router, prefer the dedicated [Next.js integration guide](/docs/integrations/next) and the [Next examples](/docs/examples/next-minimal).
## Source code
[Browse the example on GitHub](https://github.com/AminZoubaa/permix/tree/main/examples/react)
# Role-based Example
URL: https://aminzoubaa.github.io/permix/docs/examples/role-based
A minimal React example that maps one selected role to a Permix ruleset
## Purpose
`examples/role-based` is the smallest role-to-rules mapping example in the repository. It shows the classic "pick a role, load its permissions, and render UI from that snapshot" flow without extra framework layers.
## What it demonstrates
* a typed Permix template with `post` and `user` entities
* one selected role mapped to one static ruleset
* a React hook for reading permissions in components
* the `Check` component for declarative UI gating
## How the example works
The example defines `admin` and `user` role rules up front and selects one of them inside `setupPermissions()`. That makes it a good baseline for understanding the original single-role pattern before moving to merged multi-role rules.
```ts title="lib/permix.ts"
const rolesMap = {
admin: adminPermissions,
user: userPermissions,
}
export function setupPermissions() {
const role = 'admin'
const permissions = rolesMap[role]
return permix.setup(permissions)
}
```
## Run it
```bash
cd examples/role-based
pnpm dev
```
## Key files
* `src/lib/permix.ts`: typed template, role map, and setup function
* `src/hooks/use-permissions.ts`: React hook wrapper around `usePermix`
* `src/App.tsx`: role-based checks rendered in the UI
## When to use it
Start here if you want to understand the original role-based ergonomics in the smallest possible surface area. If you need multiple roles to merge, use the newer Next.js demos instead of copying this exact single-role shape.
## Source code
[Browse the example on GitHub](https://github.com/AminZoubaa/permix/tree/main/examples/role-based)
# Solid Example
URL: https://aminzoubaa.github.io/permix/docs/examples/solid
A Solid example with signal-based checks and object-aware rules
## Purpose
`examples/solid` mirrors the React and Vue examples but uses Solid primitives. It is the clearest reference for how Permix behaves with signals and effects instead of React state or Vue refs.
## What it demonstrates
* Solid integration with `PermixProvider` and `usePermix`
* `createEffect()`-driven setup after the current user resolves
* signal-friendly `isReady()` and `check(...)` usage
* declarative UI gating with the Solid `Check` component
## Core idea
The permission rule is the same ownership check used in the React and Vue samples:
```ts title="lib/permix.ts"
edit: post => post?.authorId === user.id,
```
The UI reads permissions through signals:
```tsx title="App.tsx"
{isReady() ? 'Yes' : 'No'}
{check('post', 'edit', post) ? 'Yes' : 'No'}
```
## Run it
```bash
cd examples/solid
pnpm dev
```
## Key files
* `src/lib/permix.ts`: Solid Permix instance and generated `Check`
* `src/hooks/permissions.ts`: Solid wrapper around `usePermix`
* `src/App.tsx`: effect-driven setup and per-post checks
## When to use it
Use this as the reference implementation when you need Solid-specific ergonomics but the same typed permission model as the React and Vue examples.
## Source code
[Browse the example on GitHub](https://github.com/AminZoubaa/permix/tree/main/examples/solid)
# Vue Example
URL: https://aminzoubaa.github.io/permix/docs/examples/vue
A Vue example with composables, plugin setup, and object-aware checks
## Purpose
`examples/vue` shows the official Vue adapter in its simplest realistic form. A user is loaded, the Permix instance is set up from that user, and components reactively check whether the current post can be edited.
## What it demonstrates
* the `permixPlugin` for app-wide access
* a custom `usePermissions()` composable
* watching the user and refreshing permissions when it changes
* slot-based fallback rendering with the Vue `Check` component
## Core idea
The example sets up one readable permission and one data-aware predicate:
```ts title="lib/permix.ts"
return permix.setup({
post: {
read: true,
edit: post => post?.authorId === user.id,
},
})
```
Inside the template, the `Check` component exposes an `otherwise` slot for denied UI:
```vue title="App.vue"
I can edit a post inside the Check component
I don't have permission to edit a post inside the Check component
```
## Run it
```bash
cd examples/vue
pnpm dev
```
## Key files
* `src/lib/permix.ts`: Vue Permix instance and generated `Check` component
* `src/composables/permissions.ts`: composable wrapper around `usePermix`
* `src/App.vue`: watch-based setup and post-level checks
## When to use it
Use this example if you want a Vue-first integration reference without SSR or router complexity.
## Source code
[Browse the example on GitHub](https://github.com/AminZoubaa/permix/tree/main/examples/vue)
# Check
URL: https://aminzoubaa.github.io/permix/docs/guide/check
Learn how to check permissions in your application
## Overview
Permix provides `check` and `checkAsync` for single permission checks, plus `checkSome`, `checkEvery`, `checkSomeAsync`, and `checkEveryAsync` for grouped checks.
## `check`
The `check` method allows you to verify if certain actions are permitted. It returns a boolean indicating whether the action is allowed:
```ts
permix.check('post', 'create') // returns true/false
```
## Array
You can check multiple actions at once by passing an array of actions. All actions must be permitted for the check to return true:
```ts
// Check if both create and read are allowed
permix.check('post', ['create', 'read']) // returns true if both are allowed
```
## All
Use the special 'all' keyword to verify if all possible actions for an entity are permitted:
```ts
// Check if all actions are allowed for posts
permix.check('post', 'all') // returns true only if all actions are permitted
```
## Any
Use the special 'any' keyword to verify if any of the actions for an entity are permitted:
```ts
// Check if any action is allowed for posts
permix.check('post', 'any') // returns true if any action is permitted
```
## `checkAsync`
When you need to ensure permissions are ready before checking, use `checkAsync`. This is useful when permissions might be set up asynchronously:
```ts
setTimeout(() => {
permix.setup({
post: { create: true }
})
}, 1000)
await permix.checkAsync('post', 'create') // waits for setup
```
In most cases you should use `check` instead of `checkAsync`. `checkAsync` is useful when you need to ensure permissions are ready before checking, for example in route middleware.
## Grouped Checks
Use `checkSome` when any one of several permission tuples should grant access:
```ts
permix.checkSome(
{ entity: 'reports', action: 'read' },
{ entity: 'billing', action: 'refund' },
) // true if at least one check passes
```
Use `checkEvery` when all listed permission tuples must pass:
```ts
permix.checkEvery(
{ entity: 'post', action: 'read' },
{ entity: 'post', action: 'delete' },
) // true only if every check passes
```
Async variants are also available when you need to wait for readiness first:
```ts
await permix.checkSomeAsync(
{ entity: 'reports', action: 'read' },
{ entity: 'billing', action: 'refund' },
)
```
## Data-Based
You can define permissions that depend on the data being accessed:
```ts
permix.setup({
post: {
// Only allow updates if user is the author
update: post => post.authorId === currentUserId,
// Static permission
read: true
}
})
// Check with data
const post = { id: '1', authorId: 'user1' }
permix.check('post', 'update', post) // true if currentUserId === 'user1'
```
You still can check permissions without providing the data, but it will return `false` in this case.
## Type Safety
Permix provides full type safety for your permissions:
```ts twoslash
import { createPermix } from '@aminzoubaa/permix'
const permix = createPermix<{
post: {
action: 'create' | 'update'
}
}>()
// @errors: 2345
// This will cause a TypeScript error but will return false
permix.check('post', 'invalid-action')
permix.check('invalid-entity', 'create')
```
# Events
URL: https://aminzoubaa.github.io/permix/docs/guide/events
Learn how to handle permission updates in your application
## Overview
Permix provides an event system that allows you to react to permission changes in your application. Each event provides type-safe data and hooks to register handlers.
## Usage
You can register event handlers using the `hook` and `hookOnce` methods:
```ts
const permix = createPermix<{
post: {
action: 'create' | 'read'
}
}>()
// The handler will be called every time setup is executed
permix.hook('setup', () => {
console.log('Permissions were updated')
})
// The handler will be called only once
permix.hookOnce('setup', () => {
console.log('Permissions were updated once')
})
// Calling `setup` method triggers the `setup` event
// and `ready` event if called on the client side
permix.setup({
post: {
create: true,
read: true
}
})
```
Available events:
* `setup` - Triggered when permissions are updated through the `setup` method.
* `ready` - Triggered when the permissions are ready to be used.
* `hydrate` - Triggered when the permissions are hydrated from the server.
* `invalidate` - Triggered when the current permission state is explicitly invalidated.
## Invalidate
Use `invalidate()` when the current client-side permission state should no longer be trusted, for example after logout, a team switch, or a server event telling the client that roles changed:
```ts
permix.hook('invalidate', () => {
console.log('Permissions were invalidated')
})
permix.invalidate()
```
# Hydration (SSR)
URL: https://aminzoubaa.github.io/permix/docs/guide/hydration
Learn how to hydrate and dehydrate permissions in your application
## Overview
Hydration is the process of converting server-side state into client-side state. In Permix, hydration allows you to serialize permissions on the server and restore them on the client.
Function-based permissions are converted to `null` during dehydration since functions cannot be serialized to JSON. If the dehydrated snapshot contains any `null` values, call `setup()` on the client immediately after `hydrate()` to fully restore those rules.
Boolean-only snapshots are considered complete, so after `hydrate()` they become ready on the client immediately.
## Usage
Permix provides two instance methods for handling hydration:
* `dehydrate()` - Converts the current permissions state into a JSON-serializable format
* `hydrate(state)` - Restores permissions from a previously dehydrated state
```ts twoslash
import { createPermix } from '@aminzoubaa/permix'
const permix = createPermix<{
post: {
dataType: { isPublic: boolean }
action: 'create' | 'read'
}
}>()
// Set up initial permissions
permix.setup({
post: {
create: true,
read: post => !!post?.isPublic
}
})
// Dehydrate permissions to JSON
const state = permix.dehydrate()
// Result: { post: { create: true, read: null } }
// Later, hydrate permissions from the state
permix.hydrate(state)
```
## Server-Side Rendering
Hydration is particularly useful in server-side rendering scenarios where you want to transfer permissions from the server to the client:
```ts twoslash
import { createPermix } from '@aminzoubaa/permix'
const serverPermix = createPermix<{
post: {
dataType: { isPublic: boolean }
action: 'create' | 'read'
}
}>()
serverPermix.setup({
post: {
create: true,
read: post => !!post?.isPublic,
},
})
const dehydratedState = serverPermix.dehydrate()
// { post: { create: true, read: null } }
const clientPermix = createPermix<{
post: {
dataType: { isPublic: boolean }
action: 'create' | 'read'
}
}>()
clientPermix.hydrate(dehydratedState)
clientPermix.setup({
post: {
create: true,
read: post => !!post?.isPublic,
},
})
```
# Instance
URL: https://aminzoubaa.github.io/permix/docs/guide/instance
Learn how to create a new Permix instance
## Overview
Instance is the main entry point for Permix that will check permissions in every returned method. To create an instance, you need to use the `createPermix` function.
## TypeScript
Permix is built with TypeScript, providing type safety and validation. Using TypeScript enables autocompletion and compile-time checks for your permission definitions.
```ts twoslash title="/lib/permix.ts"
import { createPermix } from '@aminzoubaa/permix'
const permix = createPermix<{
post: {
action: 'create' | 'edit'
}
}>()
```
### Generic type
Permix instance accepts a generic type to define permissions.
#### `action`
Union type of all actions you want to check on the entity.
```ts twoslash title="/lib/permix.ts"
import { createPermix } from '@aminzoubaa/permix'
const permix = createPermix<{
post: {
action: 'create' | 'edit'
// ^^^^^^
}
}>()
```
#### `dataType`
Not required, but recommended.
To define a type of your entities, you can pass the `dataType` property to a generic type. This is useful if you want to check permissions on a specific entity otherwise the type will be `unknown`.
```ts twoslash title="/lib/permix.ts"
import { createPermix } from '@aminzoubaa/permix'
interface Post {
id: string
author: string
content: string
}
const permix = createPermix<{
post: {
dataType: Post
// ^^^^^^^^
action: 'create' | 'edit'
}
}>()
permix.setup({
post: {
create: true,
edit: post => post?.author === 'John Doe'
// ^?
}
})
const somePost: Post = {
id: '1',
author: 'John Doe',
content: 'Hello World'
}
const canEdit = permix.check('post', 'edit') // false
const canEditWithPost = permix.check('post', 'edit', somePost) // true
```
#### `dataRequired`
Not required, defaults to `false`.
By default, when you define a `dataType`, the data parameter in permission checks is optional. You can make it required by setting `dataRequired: true`. This ensures that permission checks for that entity must always include the data parameter.
```ts twoslash title="/lib/permix.ts"
import { createPermix } from '@aminzoubaa/permix'
interface Post {
id: string
author: string
content: string
}
const permix = createPermix<{
post: {
dataType: Post
dataRequired: true
// ^^^^^^^^^^^^
action: 'create' | 'edit'
}
}>()
permix.setup({
post: {
create: true,
edit: post => post.author === 'John Doe'
// ^?
}
})
const somePost: Post = {
id: '1',
author: 'John Doe',
content: 'Hello World'
}
// @errors: 2554
const canCreate = permix.check('post', 'create')
const canEdit = permix.check('post', 'edit', somePost) // ✅ Valid
```
When `dataRequired` is `true`, TypeScript will enforce that you must pass the data parameter when checking permissions for that entity. This is useful when your permission logic always depends on the entity data and you want to prevent accidental calls without the required data.
#### `PermixDefinition`
You can use `PermixDefinition` type to define your permissions separately from the instance.
```ts twoslash title="/lib/permix.ts"
import type { PermixDefinition, PermixRules } from '@aminzoubaa/permix'
import { createPermix } from '@aminzoubaa/permix'
type PermissionsDefinition = PermixDefinition<{
post: {
action: 'create' | 'edit'
}
}>
async function getRules(): Promise> {
// get user or something like that
return {
post: {
create: true,
edit: false
}
}
}
const permix = createPermix()
permix.setup(await getRules())
```
### Return type
Each Permix instance provides a list of methods to manage and check permissions. These methods are documented in detail on their separate pages.
```ts twoslash title="/lib/permix.ts"
import { createPermix } from '@aminzoubaa/permix'
const permix = createPermix()
// @noErrors
permix.
// ^|
```
## JavaScript
Not using TypeScript? Permix works perfectly fine even with plain JavaScript.
```ts title="/lib/permix.js"
const permix = createPermix()
```
## Initial Rules
You can provide initial rules when creating a Permix instance. This allows you to set up permissions immediately without calling `setup` separately.
```ts twoslash title="/lib/permix.ts"
import { createPermix } from '@aminzoubaa/permix'
const permix = createPermix<{
post: {
action: 'create' | 'edit'
}
}>({
post: {
create: true,
edit: false
}
})
// Permissions are immediately available
console.log(permix.check('post', 'create')) // true
console.log(permix.isReady()) // true
```
This is equivalent to:
```ts twoslash title="/lib/permix.ts"
import { createPermix } from '@aminzoubaa/permix'
const permix = createPermix<{
post: {
action: 'create' | 'edit'
}
}>()
permix.setup({
post: {
create: true,
edit: false
}
})
```
## Request-Scoped Instances
If you need a fresh instance for each server request, you can derive one with `resolve()`. This is useful in SSR or route handlers where permissions must not leak between concurrent requests.
```ts twoslash title="/lib/permix.ts"
import { createPermix } from '@aminzoubaa/permix'
const permix = createPermix<{
post: {
action: 'create' | 'read'
}
}>()
const requestPermix = permix.resolve({
post: {
create: true,
read: true,
}
})
requestPermix.check('post', 'create') // true
permix.check('post', 'create') // false
```
Initial rules are useful when you have permissions that are known at initialization time and don't need to be loaded asynchronously.
You still should pass generic type to `createPermix` even if you provide initial rules. Otherwise, Permix will not be able to validate your permissions.
# Migration To v4
URL: https://aminzoubaa.github.io/permix/docs/guide/migration-v4
Breaking changes, migration steps, and why Permix 4 changes SSR and hydration semantics
## Why this release is major
Permix `4.0.0` fixes real correctness issues around SSR hydration, multi-role composition, and request isolation in Next.js. Some of those fixes change public types and expectations, so this release is intentionally a major.
## Breaking Changes
### `PermixStateJSON` now contains `null`
Dehydrated function rules are now represented as `null` instead of being flattened to `false`.
```ts
type Before = boolean
type After = boolean | null
```
That means code reading a dehydrated snapshot should now distinguish between:
* `true`: allowed
* `false`: explicitly denied
* `null`: not serializable, needs live rules to be restored
### Hydration readiness is explicit
If the dehydrated snapshot is fully boolean, `hydrate()` makes the client ready immediately.
If the snapshot contains `null`, hydration restores only the boolean shell. Call `setup()` after `hydrate()` to restore the live function rules:
```tsx
permix.hydrate(state)
permix.setup(liveRules)
```
### Next.js guidance changed
If you previously used one mutable server singleton and repeatedly called `setup()` in the App Router, migrate away from that pattern. Use request-scoped snapshots or isolated instances instead.
## Migration Steps
### 1. Update snapshot handling code
If you store or inspect dehydrated state, update its type assumptions:
```ts
import type { PermixStateJSON } from '@aminzoubaa/permix'
function isCompleteSnapshot(state: PermixStateJSON) {
return Object.values(state).every(entity =>
Object.values(entity).every(value => value !== null),
)
}
```
### 2. Restore function rules after client hydration
If your rules contain predicates, keep the server authoritative and restore the client rules explicitly when needed:
```tsx
'use client'
import { createPermix } from '@aminzoubaa/permix'
import { PermixHydrate, PermixProvider } from '@aminzoubaa/permix/react'
import { useEffect, useState } from 'react'
const [permix] = useState(() => createPermix())
useEffect(() => {
permix.setup(liveRules)
}, [permix, liveRules])
return (
{children}
)
```
### 3. Replace single-role selection with merged rules
If a user can have several roles, compose them before setup:
```ts
import { mergeRules } from '@aminzoubaa/permix'
permix.setup(mergeRules(authorRules, reviewerRules, supportRules))
```
### 4. Replace shared server singletons in Next.js
Old pattern:
```ts
export const permix = createPermix()
export async function getPermissions() {
permix.setup(await loadRules())
return permix
}
```
Recommended pattern:
```ts
import { createPermixSnapshot } from '@aminzoubaa/permix/next'
export async function getPermissionsSnapshot() {
return createPermixSnapshot(await loadRules())
}
```
Or, if you already have a base instance and need isolation:
```ts
const requestPermix = permix.resolve(await loadRules())
```
## Benefits after migrating
* Boolean hydration no longer gets stuck behind a loading state.
* Multiple roles can express a real union of capabilities.
* Next.js server requests stop sharing mutable permission state.
* Cache invalidation flows are easier to reason about because the server snapshot is explicit.
## Related Guides
* [Next.js](/docs/integrations/next)
* [Hydration (SSR)](/docs/guide/hydration)
* [Setup](/docs/guide/setup)
# Ready State
URL: https://aminzoubaa.github.io/permix/docs/guide/ready
Learn how to use the `isReady()` method to check if permissions are ready to use.
## Overview
Sometimes you need to know when permissions are ready to use. For example, you might want to wait for permissions to be ready before rendering a component. That's where the `isReady()` and `isReadyAsync()` methods come in.
`isReady()` and `isReadyAsync()` always return `false` on the server. On the client they become `true` after the first successful `setup()` call or after hydrating a complete boolean-only snapshot.
## Usage
### Basic
Permix provides an `isReady()` method to check if permissions have been properly initialized on the client side:
```ts twoslash
import { createPermix } from '@aminzoubaa/permix'
const permix = createPermix()
console.log(permix.isReady()) // false
// After setup completes
permix.setup({
post: {
create: true,
read: true
}
})
console.log(permix.isReady()) // true
```
### Async
If you need to wait for permissions to be ready in an async context, you can use the `isReadyAsync()` method. This returns a promise that resolves when permissions are ready:
```ts
import { createPermix } from '@aminzoubaa/permix'
const permix = createPermix()
async function init() {
await permix.isReadyAsync()
// Permissions are now ready to use
const canCreate = permix.check('post', 'create')
}
```
### SSR
This is particularly useful in SSR applications when using function-based permissions, since the dehydration process converts all function permissions to `null` until they are properly restored on the client.
Read more about [hydration](/guide/hydration) to learn how to transfer permissions from the server to the client.
```ts
import { dehydrate, hydrate, createPermix } from '@aminzoubaa/permix'
import { permix } from './permix'
permix.setup({
post: {
create: true,
read: post => post.isPublic
}
})
// Dehydrate permissions on the server
const state = dehydrate(permix)
// { post: { create: true, read: null } }
// Rehydrate permissions on the client
hydrate(permix, state)
const canRead = permix.check('post', 'read', { isPublic: true }) // false
permix.setup({
post: {
create: true,
read: post => post.isPublic
}
})
permix.hook('ready', () => {
const canRead = permix.check('post', 'read', { isPublic: true })
console.log(canRead) // true
})
```
# Setup
URL: https://aminzoubaa.github.io/permix/docs/guide/setup
Learn how to setup permissions in your project
## Overview
After creating Permix instance, you need to define permissions with `setup` method. You can call `setup` in any time with any permissions and Permix will replace the previous permissions.
You always should describe all permissions in the `setup` method that was defined in the Permix generic type.
For role separation, you can use the [`template`](/docs/guide/template) method.
If a user can have multiple roles, merge those rule sets before calling `setup()` instead of picking a single role. Permix exposes `mergeRules()` for this with logical OR semantics.
### Object definition
```ts
const permix = createPermix<{
post: {
action: 'create'
}
comment: {
action: 'create' | 'update'
}
}>()
permix.setup({
post: {
create: true,
},
comment: {
create: true,
update: true,
}
})
```
You can also use `enum` based permissions. See the [Enum-based example](/docs/examples/enum-based) for a guided walkthrough.
## Initial
You can set up initial rules directly when creating a Permix instance by passing them as the first parameter to `createPermix`.
```ts twoslash
import { createPermix } from '@aminzoubaa/permix'
const permix = createPermix<{
post: {
action: 'create' | 'read'
}
comment: {
action: 'create' | 'read' | 'update'
}
}>({
post: {
create: true,
read: true,
},
comment: {
create: false,
read: true,
update: true,
},
})
// The instance is immediately ready to use
permix.check('post', 'create') // true
permix.isReady() // true
```
When using initial rules, the Permix instance is immediately ready to use without calling `setup` first.
## Multiple Roles
When a user can belong to more than one role, compose those permissions into a single rules object before applying them.
```ts twoslash
import type { PermixDefinition, PermixRules } from '@aminzoubaa/permix'
import { createPermix, mergeRules } from '@aminzoubaa/permix'
type Definition = PermixDefinition<{
post: {
action: 'create' | 'read'
}
reports: {
action: 'read'
}
}>
const permix = createPermix()
const billingRules: PermixRules = {
post: {
create: false,
read: true,
},
reports: {
read: false,
},
}
const supportRules: PermixRules = {
post: {
create: false,
read: true,
},
reports: {
read: true,
},
}
permix.setup(mergeRules(billingRules, supportRules))
```
## Type-Based
When creating a Permix instance, you can define entities that will be used in the `setup` method for each related entity. This allows you to check permissions for specific data entities. So instead of `boolean` you can use functions to check permissions.
```ts twoslash
import { createPermix } from '@aminzoubaa/permix'
interface Post {
id: string
authorId: string
}
const permix = createPermix<{
post: {
dataType: Post
// ^^^^^^^^
action: 'update'
}
comment: {
action: 'update'
}
}>()
// @noErrors
permix.setup({
post: {
update: post => post.
// ^|
}
comment: {
update: comment => c
// ^?
}
})
```
### Required
By default `dataType` providing optional type to callbacks but you can make it required by adding `dataRequired: true`.
```ts twoslash
import { createPermix } from '@aminzoubaa/permix'
interface Post {
id: string
authorId: string
}
interface Comment {
id: string
postId: string
}
const permix = createPermix<{
post: {
dataType: Post
dataRequired: true
action: 'update'
}
comment: {
dataType: Comment
action: 'update'
}
}>()
// @noErrors
permix.setup({
post: {
update: post => !!post.authorId
// ^?
}
comment: {
update: comment => !!comment.postId
// ^?
}
})
```
### unknown
If you cannot define entity types in the Permix instance, the types will be `unknown` by default, but you can still define them later in the `setup` method.
This approach is not recommended as it reduces type safety and IDE support.
```ts twoslash
import { createPermix } from '@aminzoubaa/permix'
const permix = createPermix<{
comment: {
action: 'update'
}
}>()
// @noErrors
permix.setup({
comment: {
update: (comment: { authorId: string }) => comment.authorId === user.id
}
})
```
## Dynamic
You can use async functions to fetch permissions from external sources and then set them up.
See the [template](/docs/guide/template) for more examples and patterns.
```ts
import { createPermix } from '@aminzoubaa/permix'
const permix = createPermix<{
post: {
action: 'create' | 'read' | 'update' | 'delete'
}
comment: {
action: 'create' | 'read' | 'update' | 'delete'
}
}>()
// Fetch permissions from API
async function loadUserPermissions(userId: string) {
const permissions = await getPermissionsFromAnyPlace()
permix.setup({
post: {
create: permissions.includes('post:create'),
read: permissions.includes('post:read'),
update: permissions.includes('post:update'),
delete: permissions.includes('post:delete'),
},
comment: {
create: permissions.includes('comment:create'),
read: permissions.includes('comment:read'),
update: permissions.includes('comment:update'),
delete: permissions.includes('comment:delete'),
},
})
}
// Usage
await loadUserPermissions('user-123')
```
## Getting Rules
You can get the current rules from an existing Permix instance using the `getRules` function:
```ts
import { getRules } from '@aminzoubaa/permix'
// Get current rules
const rules = getRules(permix)
```
The `getRules` function returns the exact rules object that was set using `setup`, including any permission functions.
# Template
URL: https://aminzoubaa.github.io/permix/docs/guide/template
Learn how to define permissions using templates
## Overview
Permix provides a `template` method that allows you to define permissions in a separate location from where they are set up. This is useful for organizing permission definitions and reusing them across different parts of your application.
Templates are validated when they are created, ensuring your permission definitions are correct before runtime.
## Basic Usage
The simplest way to use templates is to define static permissions:
```ts
const adminPermissions = permix.template({
post: {
create: true,
read: true
}
})
// Later, use the template to setup permissions
permix.setup(adminPermissions())
```
## Dynamic Templates
Templates can accept parameters to create dynamic permissions based on runtime values:
```ts
interface User {
id: string
role: string
}
const userPermissions = permix.template(({ id: userId }: User) => ({
post: {
create: true,
read: true,
update: post => post?.authorId === userId
}
}))
// Use with specific user data
const user = await getUser()
permix.setup(userPermissions(user))
```
## Type Safety
Templates maintain full type safety from your Permix instance definition:
```ts twoslash
import { createPermix } from '@aminzoubaa/permix'
const permix = createPermix<{
post: {
action: 'create'
}
}>()
// @errors: 2353
// This will throw an error
const invalidTemplate = permix.template({
post: {
edit: true
}
})
```
## Role-Based Example
Templates are particularly useful for role-based permission systems:
```ts
const editorPermissions = permix.template({
post: {
create: true,
read: true,
update: post => !post?.published,
delete: post => !post?.published
}
})
const userPermissions = permix.template(({ id: userId }: User) => ({
post: {
create: false,
read: true,
update: post => post?.authorId === userId,
delete: false
}
}))
// Setup based on user role
function setupPermissions() {
const user = await getUser()
const permissionsMap = {
editor: () => editorPermissions(),
user: () => userPermissions(user)
}
return permix.setup(permissionsMap[user.role]())
}
```
## Standalone Templates
You can define permission templates outside of the Permix instance using the `PermixRules` type. This is useful when you want to organize your permission logic in separate files:
```ts twoslash
import type { PermixRules, PermixDefinition } from '@aminzoubaa/permix'
import { createPermix } from '@aminzoubaa/permix'
// Define your Permix definition type
type Definition = PermixDefinition<{
post: {
dataType: { id: string; authorId: string }
action: 'create' | 'read' | 'update' | 'delete'
}
}>
// It can be in separate file and imported here
const permix = createPermix()
// Create a standalone template function
function userPermissions(userId: string, role: 'admin' | 'user'): PermixRules {
return {
post: {
create: role === 'admin',
read: true,
update: role === 'admin' ? true : (post) => post?.authorId === userId,
delete: role === 'admin'
}
}
}
// Later, use it with your Permix instance
const permissions = userPermissions('1', 'admin')
permix.setup(permissions)
```
This approach allows you to:
* Keep permission logic separate from your Permix instance
* Reuse permission templates across different parts of your application
* Maintain full type safety with your Permix definition
# Better Auth
URL: https://aminzoubaa.github.io/permix/docs/integrations/better-auth
Learn how to use Permix with Better Auth
## Overview
Permix provides a [Better Auth](https://www.better-auth.com/) plugin that automatically maps authenticated sessions to permission rules. It exposes a `GET /permix/permissions` endpoint that returns the current user's dehydrated permissions, making it easy to sync permissions between server and client.
Before getting started with Better Auth integration, make sure you've completed the initial setup steps in the [Quick Start](/docs/quick-start) guide.
## Server Setup
Create a Permix instance with a `rules` callback that receives the Better Auth session and returns your permission rules. Then add `permix.plugin` to your Better Auth configuration:
```ts title="auth.ts"
import { betterAuth } from 'better-auth'
import { createPermix } from '@aminzoubaa/permix/better-auth'
interface Post {
id: string
authorId: string
title: string
content: string
}
// Create your Permix instance with rules based on the session
const permix = createPermix<{
post: {
dataType: Post
action: 'create' | 'read' | 'update' | 'delete'
}
}>({
rules: ({ user }) => ({
post: {
create: user.role === 'admin',
read: true,
update: user.role === 'admin',
delete: user.role === 'admin',
},
}),
})
// Add the plugin to your Better Auth instance
export const auth = betterAuth({
// ...your config
plugins: [permix.plugin],
})
```
This automatically registers a `GET /permix/permissions` endpoint on your auth server. The endpoint requires authentication (returns 401 if unauthenticated) and returns the dehydrated permission state for the current user.
## Client Setup
Add the `permixClient` plugin to your Better Auth client to get typed access to the permissions endpoint:
```ts title="auth-client.ts"
import { createAuthClient } from 'better-auth/client'
import { permixClient } from '@aminzoubaa/permix/better-auth'
import type { permix } from './auth' // import the type of your server permix instance
const authClient = createAuthClient({
plugins: [permixClient()],
})
// Fetch the current user's permissions
const { data: permissions, error } = await authClient.permix.permissions()
```
## Hydrating on the Client
The permissions endpoint returns a dehydrated state that you can hydrate into a client-side Permix instance. This works with any frontend framework integration (React, Vue, Solid):
```ts title="permissions.ts"
import { createPermix } from '@aminzoubaa/permix'
const permix = createPermix<{
post: {
action: 'create' | 'read' | 'update' | 'delete'
}
}>()
// Fetch and hydrate permissions
const { data } = await authClient.permix.permissions()
if (data) {
permix.hydrate(data)
}
```
See the [Hydration](/docs/guide/hydration) guide for more details on how hydration works with SSR frameworks.
## Using Templates
Permix provides a template helper to create reusable permission rule sets:
```ts title="auth.ts"
const permix = createPermix({
rules: ({ user }) => {
if (user.role === 'admin') {
return adminTemplate()
}
return userTemplate()
},
})
// Create role-based templates
const adminTemplate = permix.template({
post: {
create: true,
read: true,
update: true,
delete: true,
},
})
const userTemplate = permix.template({
post: {
create: false,
read: true,
update: false,
delete: false,
},
})
```
## Reusing Rules with Framework Middleware
The `createPermix` return value exposes the `rules` function so you can reuse the same session-to-permissions mapping in your framework-specific middleware. This is useful when you need to protect application routes (not just Better Auth endpoints):
```ts title="app.ts"
import { Hono } from 'hono'
import { createPermix as createHonoPermix } from '@aminzoubaa/permix/hono'
import { auth, permix } from './auth'
const honoPermix = createHonoPermix()
const app = new Hono()
// Reuse the same rules callback from your Better Auth plugin
app.use(honoPermix.setupMiddleware(async ({ c }) => {
const session = await auth.api.getSession({
headers: c.req.raw.headers,
})
if (!session) {
return {
post: { create: false, read: false, update: false, delete: false },
}
}
return permix.rules(session)
}))
app.post('/posts', honoPermix.checkMiddleware('post', 'create'), (c) => {
return c.json({ success: true })
})
```
## Advanced Usage
### Async Permission Rules
The `rules` callback supports async functions, useful when permissions depend on data beyond the session (e.g., team membership, feature flags):
```ts
const permix = createPermix({
rules: async ({ user }) => {
const teamPermissions = await getTeamPermissions(user.id)
return {
post: {
create: teamPermissions.canCreatePosts,
read: true,
update: teamPermissions.canUpdatePosts,
delete: teamPermissions.canDeletePosts,
},
}
},
})
```
### Using with Better Auth's Admin Plugin
If you're using Better Auth's [admin plugin](https://www.better-auth.com/docs/plugins/admin), you can use the `role` field it adds to the user object:
```ts
import { betterAuth } from 'better-auth'
import { admin } from 'better-auth/plugins'
import { createPermix } from '@aminzoubaa/permix/better-auth'
const permix = createPermix({
rules: ({ user }) => {
const role = (user as { role?: string }).role ?? 'user'
return {
post: {
create: role === 'admin' || role === 'editor',
read: true,
update: role === 'admin' || role === 'editor',
delete: role === 'admin',
},
}
},
})
export const auth = betterAuth({
plugins: [admin(), permix.plugin],
})
```
# Elysia
URL: https://aminzoubaa.github.io/permix/docs/integrations/elysia
Learn how to use Permix with Elysia
## Overview
Permix provides integration for Elysia that allows you to easily check permissions in your routes. The integration can be created using the `createPermix` function.
Before getting started with Elysia integration, make sure you've completed the initial setup steps in the [Quick Start](/docs/quick-start) guide.
## Setup
Here's a basic example of how to use the Permix middleware with Elysia:
```ts twoslash
import { Elysia } from 'elysia'
import { createPermix } from '@aminzoubaa/permix/elysia'
interface Post {
id: string
authorId: string
title: string
content: string
}
// Create your Permix instance
const permix = createPermix<{
post: {
dataType: Post
action: 'create' | 'read' | 'update' | 'delete'
}
}>()
// Initialize Elysia
const app = new Elysia()
// Derive your permission rules
.derive(({ headers }) => {
// You can access body or other properties to determine permissions
const isAuthorized = !!headers.authorization?.slice(7)
return permix.derive({
post: {
create: true,
read: true,
update: isAuthorized,
delete: isAuthorized
}
})
})
```
The derive preserves full type safety from your Permix definition, ensuring your permission checks are type-safe.
## Checking Permissions
Use the `checkHandler` function in your Elysia routes to check permissions:
```ts
app.post('/posts', () => {
// Create post logic here
return { success: true }
}, {
beforeHandle: permix.checkHandler('post', 'create')
})
// Check multiple actions
app.put('/posts/:id', () => {
// Update post logic here
return { success: true }
}, {
beforeHandle: permix.checkHandler('post', ['read', 'update'])
})
// Check all actions
app.delete('/posts/:id', () => {
// Delete post logic here
return { success: true }
}, {
beforeHandle: permix.checkHandler('post', 'all')
})
// Check any action
app.get('/posts', () => {
// Get posts logic here
return { posts: getAllPosts() }
}, {
beforeHandle: permix.checkHandler('post', 'any')
})
```
## Accessing Permix Directly
You can access the Permix instance directly in your route handlers using the context:
```ts
app.get('/posts', ({ permix }) => {
// Check permissions manually
if (permix.check('post', 'read')) {
// User has permission to read posts
return { posts: getAllPosts() }
} else {
return { error: 'You do not have permission to read posts' }
}
})
```
The `get` function returns the Permix instance with available methods.
## Using Templates
Permix provides a template helper to create reusable permission rule sets:
```ts
// Create a template for admin permissions
const adminTemplate = permix.template({
post: {
create: true,
read: true,
update: true,
delete: true
}
})
// Create a template for regular user permissions
const userTemplate = permix.template({
post: {
create: true,
read: true,
update: false,
delete: false
}
})
// Use templates in your middleware
app.derive(({ headers }) => {
const user = await getUserFromDb(headers.authorization.slice(7))
return permix.derive(
user?.role === 'admin'
? adminTemplate
: userTemplate
)
})
```
## Custom Error Handling
By default, the middleware returns a 403 Forbidden response. You can customize this behavior by providing an `onForbidden` handler:
### Basic Error Handler
```ts
const permix = createPermix({
onForbidden: ({ context }) => {
context.set.status = 403
return { error: 'Custom forbidden message' }
}
})
```
### Dynamic Error Handler
You can also provide a handler that returns different responses based on the entity and actions:
```ts
const permix = createPermix({
onForbidden: ({ context, entity, actions }) => {
context.set.status = 403
if (entity === 'post' && actions.includes('create')) {
return {
error: `You don't have permission to ${actions.join('/')} a ${entity}`
}
}
return {
error: 'You do not have permission to perform this action'
}
}
})
```
The `onForbidden` handler receives:
* `context`: Elysia Context object
* `entity`: The entity that was checked
* `actions`: Array of actions that were checked
## Advanced Usage
### Async Permission Rules
You can use async functions in your permission setup:
```ts
app.derive(async ({ headers }) => {
// Fetch user permissions from database
const userId = headers.authorization?.slice(7)
const userPermissions = await getUserPermissions(userId)
return permix.derive({
post: {
create: userPermissions.canCreatePosts,
read: userPermissions.canReadPosts,
update: userPermissions.canUpdatePosts,
delete: userPermissions.canDeletePosts
}
})
})
```
### Dynamic Data-Based Permissions
You can check permissions based on the specific data being accessed:
```ts
app.put('/posts/:id', async ({ params, permix }) => {
const postId = params.id
const post = await getPostById(postId)
// Check if user can update this specific post
if (permix.check('post', 'update', post)) {
// Update post logic
return { success: true }
} else {
return { error: 'You cannot update this post' }
}
})
```
# Express
URL: https://aminzoubaa.github.io/permix/docs/integrations/express
Learn how to use Permix with Express
## Overview
Permix provides middleware for Express that allows you to easily check permissions in your routes. The middleware can be created using the `createPermix` function.
Before getting started with Express integration, make sure you've completed the initial setup steps in the [Quick Start](/docs/quick-start) guide.
## Setup
Here's a basic example of how to use the Permix middleware with Express:
```ts twoslash
import express from 'express'
import { createPermix } from '@aminzoubaa/permix/express'
interface Post {
id: string
authorId: string
title: string
content: string
}
// Initialize Express
const app = express()
// Create your Permix instance
const permix = createPermix<{
post: {
dataType: Post
action: 'create' | 'read' | 'update'
}
}>()
// Set up the middleware with your permission rules
app.use(permix.setupMiddleware(({ req }) => {
// You can access req.user or other properties to determine permissions
return {
post: {
create: true,
read: true,
update: false
}
}
}))
```
The middleware preserves full type safety from your Permix definition, ensuring your permission checks are type-safe.
## Checking Permissions
Use the `checkMiddleware` function in your Express routes to check permissions:
```ts
app.post('/posts', permix.checkMiddleware('post', 'create'), (req, res) => {
res.json({ success: true })
})
// Check multiple actions
app.put('/posts/:id', permix.checkMiddleware('post', ['read', 'update']), (req, res) => {
res.json({ success: true })
})
// Check all actions
app.delete('/posts/:id', permix.checkMiddleware('post', 'all'), (req, res) => {
res.json({ success: true })
})
// Check any action
app.get('/posts', permix.checkMiddleware('post', 'any'), (req, res) => {
res.json({ posts: getAllPosts() })
})
```
## Accessing Permix Directly
You can access the Permix instance directly in your route handlers using the `get` function:
```ts
app.get('/posts', (req, res) => {
const { check } = permix.get(req, res)
// Check permissions manually
if (check('post', 'read')) {
// User has permission to read posts
res.json({ posts: getAllPosts() })
} else {
res.status(403).json({ error: 'You do not have permission to read posts' })
}
})
```
The `get` function returns the Permix instance with available methods.
## Using Templates
Permix provides a template helper to create reusable permission rule sets:
```ts
// Create a template for admin permissions
const adminTemplate = permix.template({
post: {
create: true,
read: true,
update: true
}
})
// Use the template in your middleware
app.use(permix.setupMiddleware(({ req }) => {
// You can still customize the template based on request data
if (req.user?.role === 'admin') {
return adminTemplate
}
return {
post: {
create: false,
read: true,
update: false
}
}
}))
```
## Custom Error Handling
By default, the middleware returns a 403 Forbidden response. You can customize this behavior by providing an `onForbidden` handler:
### Basic Error Handler
```ts
const permix = createPermix({
onForbidden: ({ res }) => {
res.status(403).json({
error: 'Custom forbidden message',
})
}
})
```
### Dynamic Error Handler
You can also provide a handler that returns different responses based on the entity and actions:
```ts
const permix = createPermix({
onForbidden: ({ res, entity, actions }) => {
if (entity === 'post' && actions.includes('create')) {
res.status(403).json({
error: `You don't have permission to ${actions.join('/')} a ${entity}`,
})
return
}
res.status(403).json({
error: 'You do not have permission to perform this action',
})
}
})
```
The `onForbidden` handler receives:
* `req`: Express Request object
* `res`: Express Response object
* `entity`: The entity that was checked
* `actions`: Array of actions that were checked
## Advanced Usage
### Async Permission Rules
You can use async functions in your permission setup:
```ts
app.use(permix.setupMiddleware(async ({ req }) => {
// Fetch user permissions from database
const userPermissions = await getUserPermissions(req.user.id)
return {
post: {
create: userPermissions.canCreatePosts,
read: userPermissions.canReadPosts,
update: userPermissions.canUpdatePosts
}
}
}))
```
### Dynamic Data-Based Permissions
You can check permissions based on the specific data being accessed:
```ts
app.put('/posts/:id', async (req, res, next) => {
const post = await getPostById(req.params.id)
const { check } = permix.get(req, res)
// Check if user can update this specific post
if (check('post', 'update', post)) {
next()
} else {
res.status(403).json({ error: 'You cannot update this post' })
}
})
```
# Fastify
URL: https://aminzoubaa.github.io/permix/docs/integrations/fastify
Learn how to use Permix with Fastify
## Overview
Permix provides a plugin for Fastify that allows you to easily check permissions in your routes. The plugin can be created using the `createPermix` function.
Before getting started with Fastify integration, make sure you've completed the initial setup steps in the [Quick Start](/docs/quick-start) guide.
## Setup
Here's a basic example of how to use the Permix plugin with Fastify:
```ts twoslash
import Fastify from 'fastify'
import { createPermix } from '@aminzoubaa/permix/fastify'
interface Post {
id: string
authorId: string
title: string
content: string
}
// Initialize Fastify
const fastify = Fastify()
// Create your Permix instance
const permix = createPermix<{
post: {
dataType: Post
action: 'create' | 'read' | 'update'
}
}>()
// Set up the plugin with your permission rules
fastify.register(permix.plugin(({ request, reply }) => {
// You can access request.user or other properties to determine permissions
return {
post: {
create: true,
read: true,
update: false
}
}
}))
```
The plugin preserves full type safety from your Permix definition, ensuring your permission checks are type-safe.
## Checking Permissions
Use the `checkHandler` function in your Fastify routes to check permissions:
```ts
fastify.post('/posts', {
preHandler: permix.checkHandler('post', 'create'),
}, (request, reply) => {
reply.send({ success: true })
})
// Check multiple actions
fastify.put('/posts/:id', {
preHandler: permix.checkHandler('post', ['read', 'update']),
}, (request, reply) => {
reply.send({ success: true })
})
// Check all actions
fastify.delete('/posts/:id', {
preHandler: permix.checkHandler('post', 'all'),
}, (request, reply) => {
reply.send({ success: true })
})
// Check any action
fastify.get('/posts', {
preHandler: permix.checkHandler('post', 'any'),
}, (request, reply) => {
reply.send({ posts: getAllPosts() })
})
```
## Accessing Permix Directly
You can access the Permix instance directly in your route handlers using the `get` function:
```ts
fastify.get('/posts', (request, reply) => {
const { check } = permix.get(request, reply)
// Check permissions manually
if (check('post', 'read')) {
// User has permission to read posts
reply.send({ posts: getAllPosts() })
} else {
reply.status(403).send({ error: 'You do not have permission to read posts' })
}
})
```
The `get` function returns the Permix instance with available methods.
## Using Templates
Permix provides a template helper to create reusable permission rule sets:
```ts
// Create a template for admin permissions
const adminTemplate = permix.template({
post: {
create: true,
read: true,
update: true
}
})
// Use the template in your middleware
fastify.register(permix.plugin(({ request }) => {
// You can still customize the template based on request data
if (request.user?.role === 'admin') {
return adminTemplate
}
return {
post: {
create: false,
read: true,
update: false
}
}
}))
```
## Custom Error Handling
By default, the plugin returns a 403 Forbidden response. You can customize this behavior by providing an `onForbidden` handler:
### Basic Error Handler
```ts
const permix = createPermix({
onForbidden: ({ reply }) => {
reply.status(403).send({
error: 'Custom forbidden message',
})
}
})
```
### Dynamic Error Handler
You can also provide a handler that returns different responses based on the entity and actions:
```ts
const permix = createPermix({
onForbidden: ({ reply, entity, actions }) => {
if (entity === 'post' && actions.includes('create')) {
reply.status(403).send({
error: `You don't have permission to ${actions.join('/')} a ${entity}`,
})
return
}
reply.status(403).send({
error: 'You do not have permission to perform this action',
})
}
})
```
The `onForbidden` handler receives:
* `request`: Fastify Request object
* `reply`: Fastify Reply object
* `entity`: The entity that was checked
* `actions`: Array of actions that were checked
## Advanced Usage
### Async Permission Rules
You can use async functions in your permission setup:
```ts
fastify.register(permix.plugin(async ({ request }) => {
// Fetch user permissions from database
const userPermissions = await getUserPermissions(request.user.id)
return {
post: {
create: userPermissions.canCreatePosts,
read: userPermissions.canReadPosts,
update: userPermissions.canUpdatePosts
}
}
}))
```
### Dynamic Data-Based Permissions
You can check permissions based on the specific data being accessed:
```ts
fastify.put('/posts/:id', {
preHandler: async (request, reply) => {
const post = await getPostById(request.params.id)
const { check } = permix.get(request, reply)
// Check if user can update this specific post
if (check('post', 'update', post)) {
return
} else {
reply.status(403).send({ error: 'You cannot update this post' })
}
}
})
```
# Hono
URL: https://aminzoubaa.github.io/permix/docs/integrations/hono
Learn how to use Permix with Hono
## Overview
Permix provides middleware for Hono that allows you to easily check permissions in your routes. The middleware can be created using the `createPermix` function.
Before getting started with Hono integration, make sure you've completed the initial setup steps in the [Quick Start](/docs/quick-start) guide.
If you are running Hono inside the Next.js App Router, also read the [Next.js guide](/docs/integrations/next). The supported pattern is `app.fetch(request)` with request-scoped rules.
## Setup
Here's a basic example of how to use the Permix middleware with Hono:
```ts twoslash
import { Hono } from 'hono'
import { createPermix } from '@aminzoubaa/permix/hono'
interface Post {
id: string
authorId: string
title: string
content: string
}
// Initialize Hono
const app = new Hono()
// Create your Permix instance
const permix = createPermix<{
post: {
dataType: Post
action: 'create' | 'read' | 'update' | 'delete'
}
}>()
// Set up the middleware with your permission rules
app.use(permix.setupMiddleware(({ c }) => {
// You can access c.get('user') or other properties to determine permissions
const user = c.get('user')
const isAdmin = user?.role === 'admin'
return {
post: {
create: true,
read: true,
update: isAdmin,
delete: isAdmin
}
}
}))
```
The middleware preserves full type safety from your Permix definition, ensuring your permission checks are type-safe.
## Checking Permissions
Use the `checkMiddleware` function in your Hono routes to check permissions:
```ts
app.post('/posts', permix.checkMiddleware('post', 'create'), (c) => {
// Create post logic here
return c.json({ success: true })
})
// Check multiple actions
app.put('/posts/:id', permix.checkMiddleware('post', ['read', 'update']), (c) => {
// Update post logic here
return c.json({ success: true })
})
// Check all actions
app.delete('/posts/:id', permix.checkMiddleware('post', 'all'), (c) => {
// Delete post logic here
return c.json({ success: true })
})
// Check any action
app.get('/posts', permix.checkMiddleware('post', 'any'), (c) => {
// Get posts logic here
return c.json({ posts: getAllPosts() })
})
```
## Accessing Permix Directly
You can access the Permix instance directly in your route handlers using the `get` function:
```ts
app.get('/posts', (c) => {
const { check } = permix.get(c)
// Check permissions manually
if (check('post', 'read')) {
// User has permission to read posts
return c.json({ posts: getAllPosts() })
} else {
return c.json({ error: 'You do not have permission to read posts' }, 403)
}
})
```
The `get` function returns the current request-scoped Permix instance, including `check`, `checkSome`, `checkEvery`, and `dehydrate`.
Only failed permission checks go through `onForbidden`. If your route throws for another reason, that error now bubbles normally instead of being silently rewritten as a forbidden response.
## Using Templates
Permix provides a template helper to create reusable permission rule sets:
```ts
// Create a template for admin permissions
const adminTemplate = permix.template({
post: {
create: true,
read: true,
update: true,
delete: true
}
})
// Create a template for regular user permissions
const userTemplate = permix.template({
post: {
create: true,
read: true,
update: false,
delete: false
}
})
// Use templates in your middleware
app.use(permix.setupMiddleware(({ c }) => {
const user = c.get('user')
if (user?.role === 'admin') {
return adminTemplate
}
return userTemplate
}))
```
## Custom Error Handling
By default, the middleware returns a 403 Forbidden response. You can customize this behavior by providing an `onForbidden` handler:
### Basic Error Handler
```ts
const permix = createPermix({
onForbidden: ({ c }) => {
return c.json({ error: 'Custom forbidden message' }, 403)
}
})
```
### Dynamic Error Handler
You can also provide a handler that returns different responses based on the entity and actions:
```ts
const permix = createPermix({
onForbidden: ({ c, entity, actions }) => {
if (entity === 'post' && actions.includes('create')) {
return c.json({
error: `You don't have permission to ${actions.join('/')} a ${entity}`
}, 403)
}
return c.json({
error: 'You do not have permission to perform this action'
}, 403)
}
})
```
The `onForbidden` handler receives:
* `c`: Hono Context object
* `entity`: The entity that was checked
* `actions`: Array of actions that were checked
## Advanced Usage
### Async Permission Rules
You can use async functions in your permission setup:
```ts
app.use(permix.setupMiddleware(async ({ c }) => {
// Fetch user permissions from database
const user = c.get('user')
const userPermissions = await getUserPermissions(user.id)
return {
post: {
create: userPermissions.canCreatePosts,
read: userPermissions.canReadPosts,
update: userPermissions.canUpdatePosts,
delete: userPermissions.canDeletePosts
}
}
}))
```
### Dynamic Data-Based Permissions
You can check permissions based on the specific data being accessed:
```ts
app.put('/posts/:id', async (c) => {
const postId = c.req.param('id')
const post = await getPostById(postId)
const { check } = permix.get(c)
// Check if user can update this specific post
if (check('post', 'update', post)) {
// Update post logic
return c.json({ success: true })
} else {
return c.json({ error: 'You cannot update this post' }, 403)
}
})
```
# Next.js
URL: https://aminzoubaa.github.io/permix/docs/integrations/next
Learn how to use Permix with the Next.js App Router
## Overview
Permix works well with the Next.js App Router when you split responsibilities clearly:
* Use `permix/next` on the server to create request-scoped instances and dehydrated snapshots.
* Use `permix/react` in client components for hydration and reactive UI checks.
* Revalidate cached permission snapshots explicitly after role changes.
Do not mutate one shared Permix server singleton across requests. In the App Router that can leak permission state between overlapping requests.
The local reference implementation for this guide lives in the Next repro app at `examples/next-app-router`.
For onboarding, start with [`next-minimal`](/docs/examples/next-minimal). For a broader multi-role application, use [`next-blog-cms`](/docs/examples/next-blog-cms). For reproductions and framework handler edge cases, use [`next-app-router`](/docs/examples/next-app-router-repros).
## Security Boundaries
These layers are not equivalent:
* API routes and route handlers are authoritative.
* Server components, pages, and layouts are the right place for hard gating with `notFound()` or redirects.
* Client checks are for UI only.
If the action must be secure, enforce it on the server even if the client also hides the UI.
## Server Snapshot
Create the effective permission rules on the server, then turn them into a dehydrated snapshot for the client:
```ts title="app/cases/page.tsx"
import { createPermixSnapshot } from '@aminzoubaa/permix/next'
import { getMergedRoleRules } from '@/lib/permissions'
import { getSessionRoles } from '@/lib/auth'
import { PermissionsClient } from './permissions-client'
export default async function Page() {
const roles = await getSessionRoles()
const { state } = createPermixSnapshot(getMergedRoleRules(roles))
return
}
```
This keeps the server authoritative while still letting the client render immediately from a serialized snapshot.
## Server Layout Guards
When an entire route segment should disappear for unauthorized users, guard it in the server layout:
```ts title="app/(protected)/layout.tsx"
import { notFound } from 'next/navigation'
import { createServerPermix } from '@aminzoubaa/permix/next'
import { getMergedRoleRules } from '@/lib/permissions'
import { getCurrentRoles } from '@/lib/session'
export default async function ProtectedLayout({ children }: { children: React.ReactNode }) {
const roles = await getCurrentRoles()
const permix = createServerPermix(getMergedRoleRules(roles))
if (!permix.check('reports', 'read')) {
notFound()
}
return <>{children}>
}
```
This keeps back/forward navigation honest because the server remains the source of truth.
## Client Hydration
On the client, continue using the React integration:
```tsx title="app/cases/permissions-client.tsx"
'use client'
import type { Permix, PermixDefinition, PermixStateJSON } from '@aminzoubaa/permix'
import { createPermix } from '@aminzoubaa/permix'
import { PermixHydrate, PermixProvider, usePermix } from '@aminzoubaa/permix/react'
import { useState } from 'react'
type AppDefinition = PermixDefinition<{
post: {
action: 'create' | 'read'
}
}>
function Content({ permix }: { permix: Permix }) {
const { check, isReady } = usePermix(permix)
if (!isReady) {
return
Loading permissions...
}
return check('post', 'create') ? : null
}
export function PermissionsClient({ state }: { state: PermixStateJSON }) {
const [permix] = useState(() => createPermix())
return (
)
}
```
If your server rules are fully boolean, hydration is enough to make the client ready immediately. If any rule was function-based, it is dehydrated to `null` and you should call `setup()` on the client to restore it.
In Next.js, do not keep a mutable `createPermix()` singleton at module scope inside the client tree. Create it lazily with `useState` or `useRef` inside the client boundary so server renders and browser navigations do not share stale permission state.
## Cache Invalidation
If you cache permission snapshots in the App Router, revalidate them when roles change:
```ts title="lib/server-permix.ts"
import { createCachedPermixSnapshot, revalidatePermixTags } from '@aminzoubaa/permix/next'
import { getMergedRoleRules } from './permissions'
import { getCurrentRoles } from './session'
export const getCachedSnapshot = createCachedPermixSnapshot(
async (permissionVersion: string) => getMergedRoleRules(await getCurrentRoles()),
{
keyParts: ['permix-snapshot'],
tags: ['permix'],
},
)
export function invalidatePermissions() {
revalidatePermixTags('permix')
}
```
Then call `invalidatePermissions()` after settings changes, a role update, or logout.
Also pass a monotonic permission version or ETag into the cached loader. That changes the cache key when permissions change and avoids relying on tag revalidation alone:
```ts
const snapshot = await getCachedSnapshot(session.permissionVersion)
```
If the page itself must reflect runtime permission changes, render it dynamically. Otherwise Next.js can serve pre-rendered HTML even when the underlying permission snapshot tag was revalidated.
## Event-Based Updates
You do not need to re-fetch user context in every component. Once the client instance exists, you can invalidate it and rehydrate or set it up again when something meaningful changes:
```ts
permix.invalidate()
permix.hook('invalidate', () => {
console.log('Permissions are no longer trusted')
})
```
Typical triggers:
* logout
* session refresh
* server-sent “roles changed” events
* switching organizations or teams
This pattern is usually a better fit than repeatedly re-reading user context inside every component.
## Grouped Checks
If a route or component should be available when any of several permission conditions matches, use grouped checks instead of manually creating many intermediate booleans:
```ts
const canOpenDashboard = permix.checkSome(
{ entity: 'reports', action: 'read' },
{ entity: 'billing', action: 'refund' },
{ entity: 'post', action: 'create' },
)
const canRunDangerousFlow = permix.checkEvery(
{ entity: 'post', action: 'read' },
{ entity: 'post', action: 'delete' },
)
```
These grouped checks are cheap. They are just a small number of synchronous in-memory permission evaluations, so using a handful of them in a route or component is not a practical runtime concern.
## Performance Notes
* Prefer one request-scoped server snapshot per request instead of many nested server `setup()` calls.
* Let the server decide hard access and send the client a ready snapshot instead of recomputing the same rules deep in the tree.
* Use `checkSome` and `checkEvery` for readability; they are just thin in-memory wrappers around normal checks.
* If a page depends on permission changes at runtime, pair cache invalidation with `router.refresh()` so the server tree is recomputed.
## Hono in Route Handlers
You can run Hono inside the App Router by using `app.fetch(request)`:
```ts title="app/api/hono/[[...route]]/route.ts"
import { Hono } from 'hono'
import { createPermix } from '@aminzoubaa/permix/hono'
import { getMergedRoleRules } from '@/lib/permissions'
import { getDemoConfig } from '@/lib/store'
const app = new Hono().basePath('/api/hono')
const permix = createPermix()
app.use('*', permix.setupMiddleware(() => {
return getMergedRoleRules(getDemoConfig().roles)
}))
app.get('/reports', permix.checkMiddleware('reports', 'read'), (c) => {
return c.json({ ok: true })
})
export const GET = (request: Request) => app.fetch(request)
```
Permix now lets genuine route errors bubble through Hono instead of incorrectly rewriting them as forbidden responses.
## tRPC in Route Handlers
For tRPC route handlers, use the fetch adapter and attach a request-scoped Permix context in your procedure middleware:
```ts title="app/api/trpc/[trpc]/route.ts"
import { initTRPC } from '@trpc/server'
import { fetchRequestHandler } from '@trpc/server/adapters/fetch'
import { createPermix } from '@aminzoubaa/permix/trpc'
import { getMergedRoleRules } from '@/lib/permissions'
import { getDemoConfig } from '@/lib/store'
const t = initTRPC.context<{ permix?: ReturnType }>().create()
const permix = createPermix()
const protectedProcedure = t.procedure.use(({ next }) => {
const config = getDemoConfig()
return next({
ctx: {
permix: permix.setup(getMergedRoleRules(config.roles)),
},
})
})
const router = t.router({
reportsRead: protectedProcedure
.use(permix.checkMiddleware('reports', 'read'))
.query(() => ({ ok: true })),
})
const handler = (request: Request) => fetchRequestHandler({
router,
endpoint: '/api/trpc',
req: request,
createContext: () => ({}),
})
export { handler as GET, handler as POST }
```
## Troubleshooting
### I changed roles but the UI still shows the old result
* Revalidate the snapshot tag on the server.
* Call `router.refresh()` if the current page depends on server data.
* If the client instance should drop trust immediately, call `permix.invalidate()`.
### I get “Rules wasn't provided”
* You are checking before `setup()` or `hydrate()` completed.
* In client components, gate on `isReady`.
* In server components, always create the snapshot before rendering the client boundary.
### Hono or tRPC says the Permix instance is missing
* Ensure the setup middleware or protected procedure runs before any permission check.
* In Hono, `setupMiddleware()` must execute on the matching route tree.
* In tRPC, `ctx.permix` must be attached before `checkMiddleware()` runs.
### My function permissions work on the server but fail on the client after hydration
* That is expected unless you restore them with `setup()` on the client.
* Only boolean snapshots are fully serializable.
### My page keeps re-rendering or feels slow after I added Permix
* Avoid a mutable module-scope singleton in the client tree.
* Create the client instance once per mounted client boundary with `useState` or `useRef`.
* Push hard permission decisions to the server and hydrate the client with the result.
# Node.js
URL: https://aminzoubaa.github.io/permix/docs/integrations/node
Learn how to use Permix with Node.js HTTP servers
## Overview
Permix provides middleware for Node.js HTTP servers that allows you to easily check permissions in your request handlers. The middleware can be created using the `createPermix` function.
Before getting started with Node.js integration, make sure you've completed the initial setup steps in the [Quick Start](/docs/quick-start) guide.
## Setup
Here's a basic example of how to use the Permix middleware with a Node.js HTTP server:
```ts twoslash
import http from 'node:http'
import { createPermix } from '@aminzoubaa/permix/node'
interface Post {
id: string
authorId: string
title: string
content: string
}
// Create your Permix instance
const permix = createPermix<{
post: {
dataType: Post
action: 'create' | 'read' | 'update' | 'delete'
}
}>()
// Create an HTTP server
const server = http.createServer(async (req, res) => {
// Parse the URL
const url = new URL(req.url || '/', `http://${req.headers.host}`)
const path = url.pathname
const method = req.method || 'GET'
const context = { req, res }
// Setup Permix with permission rules
await permix.setupMiddleware(({ req }) => {
// Determine user permissions (e.g., from headers, auth token, etc.)
const isAdmin = req.headers['x-user-role'] === 'admin'
return {
post: {
create: true,
read: true,
update: isAdmin,
delete: isAdmin
}
}
})(context)
// Route handling
if (path === '/posts' && method === 'POST') {
await permix.checkMiddleware('post', 'create')(context)
}
else if (path.startsWith('/posts/') && method === 'PUT') {
await permix.checkMiddleware('post', ['read', 'update'])(context)
}
else if (path.startsWith('/posts/') && method === 'DELETE') {
await permix.checkMiddleware('post', 'delete')(context)
}
else {
res.statusCode = 404
res.end(JSON.stringify({ error: 'Not found' }))
}
})
server.listen(3000, () => {
console.log('Server running at http://localhost:3000/')
})
```
The middleware preserves full type safety from your Permix definition, ensuring your permission checks are type-safe.
## Checking Permissions
Use the `checkMiddleware` function to check permissions for specific routes:
```ts
// Check a single action
await permix.checkMiddleware('post', 'create')(context)
// Check multiple actions
await permix.checkMiddleware('post', ['read', 'update'])(context)
// Check all actions
await permix.checkMiddleware('post', 'all')(context)
```
## Accessing Permix Directly
You can access the Permix instance directly in your request handlers using the `get` function:
```ts
http.createServer((req, res) => {
const { check } = permix.get(req, res)
// User has permission to read posts
if (check('post', 'read')) {
res.statusCode = 200
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify({ posts: getAllPosts() }))
}
})
```
The `get` function returns the Permix instance with available methods.
## Using Templates
Permix provides a template helper to create reusable permission rule sets:
```ts
// Create a template for admin permissions
const adminTemplate = permix.template({
post: {
create: true,
read: true,
update: true,
delete: true
}
})
// Create a template for regular user permissions
const userTemplate = permix.template({
post: {
create: true,
read: true,
update: false,
delete: false
}
})
// Use templates in your middleware
permix.setupMiddleware(({ req }) => {
const isAdmin = req.headers['x-user-role'] === 'admin'
if (isAdmin) {
return adminTemplate
}
return userTemplate
})(context)
```
## Custom Error Handling
By default, the middleware returns a 403 Forbidden response if the user doesn't have permission. You can customize this behavior by providing an `onForbidden` handler:
### Basic Error Handler
```ts
const permix = createPermix({
onForbidden: ({ res }) => {
res.statusCode = 403
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify({ error: 'Custom forbidden message' }))
}
})
```
### Dynamic Error Handler
You can also provide a handler that returns different responses based on the entity and actions:
```ts
const permix = createPermix({
onForbidden: ({ res, entity, actions }) => {
res.statusCode = 403
res.setHeader('Content-Type', 'application/json')
if (entity === 'post' && actions.includes('create')) {
res.end(JSON.stringify({
error: `You don't have permission to ${actions.join('/')} a ${entity}`
}))
return
}
res.end(JSON.stringify({
error: 'You do not have permission to perform this action'
}))
}
})
```
The `onForbidden` handler receives:
* `req`: Node.js IncomingMessage object
* `res`: Node.js ServerResponse object
* `entity`: The entity that was checked
* `actions`: Array of actions that were checked
## Advanced Usage
### Async Permission Rules
You can use async functions in your permission setup:
```ts
permix.setupMiddleware(async ({ req }) => {
// Extract user ID from request
const userId = req.headers['x-user-id']
// Fetch user permissions from database
const userPermissions = await getUserPermissions(userId)
return {
post: {
create: userPermissions.canCreatePosts,
read: userPermissions.canReadPosts,
update: userPermissions.canUpdatePosts,
delete: userPermissions.canDeletePosts
}
}
})(context)
```
### Dynamic Data-Based Permissions
You can check permissions based on the specific data being accessed:
```ts
http.createServer(async (req, res) => {
// Setup Permix middleware first...
// Extract post ID from URL
const url = new URL(req.url || '/', `http://${req.headers.host}`)
const pathParts = url.pathname.split('/')
const postId = pathParts[2] // e.g., /posts/123
if (req.method === 'PUT' && pathParts[1] === 'posts' && postId) {
// Fetch the post data
const post = await getPostById(postId)
// Get Permix instance
const { check } = permix.get(req, res)
// Check if user can update this specific post
if (check('post', 'update', post)) {
// Process update...
res.statusCode = 200
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify({ success: true }))
} else {
res.statusCode = 403
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify({ error: 'You cannot update this post' }))
}
}
})
```
## Integration with Web Frameworks
This integration is designed for raw Node.js HTTP servers. If you're using a web framework:
* For Express, use [Permix Express integration](/docs/integrations/express)
* For Hono, use [Permix Hono integration](/docs/integrations/hono)
# oRPC
URL: https://aminzoubaa.github.io/permix/docs/integrations/orpc
Learn how to use Permix with oRPC
## Overview
Permix provides a middleware for oRPC that allows you to easily check permissions in your middlewares. The middleware can be created using the `createPermix` function.
Before getting started with oRPC integration, make sure you've completed the initial setup steps in the [Quick Start](/docs/quick-start) guide.
## Setup
Here's a basic example of how to use the Permix middleware with oRPC:
```ts twoslash
import { os } from '@orpc/server'
import { createPermix } from '@aminzoubaa/permix/orpc'
interface Post {
id: string
title: string
}
// Initialize oRPC with context
interface Context {
user: {
id: string
role: string
}
}
const orpcPermix = os.$context()
// Create your Permix instance
const permix = createPermix<{
post: {
dataType: Post
action: 'create' | 'read' | 'update'
}
user: {
action: 'delete'
}
}>()
// Create a protected middleware with Permix
const protectedMiddleware = orpcPermix.use(({ context, next }) => {
// You can access context.user or other properties to determine permissions
const isAdmin = context.user.role === 'admin'
const p = permix.setup({
post: {
create: true,
read: true,
update: isAdmin
},
user: {
delete: isAdmin
}
})
return next({
context: {
permix: p
}
})
})
```
The middleware preserves the context and input types from your oRPC middlewares, ensuring end-to-end type safety in your API.
## Checking Permissions
Use the `checkMiddleware` function in your oRPC middlewares to check permissions:
```ts
const router = orpcPermix.router({
createPost: protectedMiddleware
.use(permix.checkMiddleware('post', 'create'))
.handler(({ context }) => {
// Create post logic here
return { success: true }
}),
updatePost: protectedMiddleware
.use(permix.checkMiddleware('post', ['read', 'update']))
.handler(({ context }) => {
// Update post logic here
return { success: true }
}),
deleteUser: protectedMiddleware
.use(permix.checkMiddleware('user', 'delete'))
.handler(({ context }) => {
// Delete user logic here
return { success: true }
})
})
```
## Accessing Permix in Middlewares
Permix is automatically added to your oRPC context, so you can access it directly:
```ts
const router = orpcPermix.router({
getPosts: protectedMiddleware
.handler(({ context }) => {
// Check permissions manually
if (context.permix.check('post', 'read')) {
// User has permission to read posts
return getAllPosts()
}
// If not explicitly blocked by middleware, you can handle permission failures here
throw new ORPCError('FORBIDDEN', {
message: 'You do not have permission to read posts'
})
})
})
```
The `context.permix` object contains one method:
* `check`: Synchronously check a permission
## Using Templates
Permix provides a template helper to create reusable permission rule sets:
```ts
// Create a template for admin permissions
const adminTemplate = permix.template({
post: {
create: true,
read: true,
update: true
},
user: {
delete: true
}
})
// Create a template for regular user permissions
const userTemplate = permix.template({
post: {
create: true,
read: true,
update: false
},
user: {
delete: false
}
})
// Use templates in your middleware
const protectedMiddleware = orpcPermix.use(({ context, next }) => {
const p = permix.setup(
context.user.role === 'admin'
? adminTemplate
: userTemplate
)
return next({
context: {
permix: p
}
})
})
```
## Custom Error Handling
By default, the middleware throws an `ORPCError` with code `FORBIDDEN`. You can customize this behavior by providing a `forbiddenError` option:
### Static Error
```ts
const permix = createPermix({
forbiddenError: new ORPCError('FORBIDDEN', {
message: 'Custom forbidden message'
})
})
```
### Dynamic Error
You can also provide a function that returns different errors based on the entity and actions:
```ts
const permix = createPermix({
forbiddenError: ({ entity, actions, context }) => {
if (entity === 'post' && actions.includes('create')) {
return new ORPCError('FORBIDDEN', {
message: `User ${context.user.id} doesn't have permission to ${actions.join('/')} a ${entity}`
})
}
return new ORPCError('FORBIDDEN', {
message: 'You do not have permission to perform this action'
})
}
})
```
The `forbiddenError` handler receives:
* `context`: Your oRPC context object
* `entity`: The entity that was checked
* `actions`: Array of actions that were checked
## Advanced Usage
### Async Permission Rules
You can use async functions in your permission setup:
```ts
const protectedMiddleware = orpcPermix.use(async ({ context, next }) => {
// Fetch user permissions from database
const userPermissions = await getUserPermissions(context.user.id)
const p = permix.setup({
post: {
create: userPermissions.canCreatePosts,
read: userPermissions.canReadPosts,
update: userPermissions.canUpdatePosts
},
user: {
delete: userPermissions.canDeleteUsers
}
})
return next({
context: {
permix: p
}
})
})
```
### Dynamic Data-Based Permissions
You can check permissions based on the specific data being accessed:
```ts
const router = orpcPermix.router({
updatePost: protectedMiddleware
.handler(async ({ input, context }) => {
const post = await getPostById(input.postId)
// Check if user can update this specific post
if (context.permix.check('post', 'update', post)) {
// Update post logic
return { success: true }
}
throw new ORPCError('FORBIDDEN', {
message: 'You cannot update this specific post'
})
})
})
```
# React
URL: https://aminzoubaa.github.io/permix/docs/integrations/react
Learn how to use Permix with React applications
## Overview
Permix provides official React integration through the `PermixProvider` component and `usePermix` hook. This allows you to manage permissions reactively in your React app.
Before getting started with React integration, make sure you've completed the initial setup steps in the [Quick Start](/docs/quick-start) guide.
## Setup
First, wrap your application with the `PermixProvider`:
```tsx title="App.tsx"
import { PermixProvider } from '@aminzoubaa/permix/react'
import { permix } from './lib/permix'
function App() {
return (
)
}
```
Remember to always pass the same Permix instance to both the `PermixProvider` and `usePermix` hook to maintain type safety.
In SSR frameworks such as Next.js App Router, keep the client-side instance inside the client tree and avoid mutating one shared server singleton across requests. On the server, prefer a fresh instance or `resolve()` per request.
## Hook
For checking permissions in your components, you can use the `usePermix` hook. And to avoid importing the hook and Permix instance in every component, you can create a custom hook:
```tsx title="hooks/use-permissions.ts"
import { usePermix } from '@aminzoubaa/permix/react'
import { permix } from '../lib/permix'
export function usePermissions() {
return usePermix(permix)
}
```
## Components
If you prefer using components, you can import the `createComponents` function from `permix/react` and create checking components:
```ts title="lib/permix.ts"
import { createComponents } from '@aminzoubaa/permix/react'
// ...
export const { Check } = createComponents(permix)
```
And then you can use the `Check` component in your components:
```tsx title="page.tsx"
export default function Page() {
return (
Will show this if a user doesn't have permission
} // Will show this if a user doesn't have permission
reverse // Will flip the logic of the permission check
>
Will show this if a user has permission
)
}
```
## Usage
Use the `usePermix` hook and checking components in your components:
```tsx title="page.tsx"
import { usePermix } from '@aminzoubaa/permix/react'
import { permix } from './lib/permix'
import { Check } from './lib/permix-components'
export default function Page() {
const post = usePost()
const { check, isReady } = usePermix(permix)
if (!isReady) {
return
)}
Can I create a post inside the Check component?
)
}
```
## Example
Start with the dedicated [React example](/docs/examples/react) for a guided walkthrough, then jump to the source code from there if you want to inspect the implementation details.
# Solid
URL: https://aminzoubaa.github.io/permix/docs/integrations/solid
Learn how to use Permix with Solid applications
## Overview
Permix provides official Solid integration through the `PermixProvider` component and `usePermix` hook. This allows you to manage permissions reactively in your Solid app.
Before getting started with Solid integration, make sure you've completed the initial setup steps in the [Quick Start](/docs/quick-start) guide.
## Setup
First, wrap your application with the `PermixProvider`:
```tsx title="App.tsx"
import { PermixProvider } from '@aminzoubaa/permix/solid'
import { permix } from './lib/permix'
function App() {
return (
)
}
```
Remember to always pass the same Permix instance to both the `PermixProvider` and `usePermix` hook to maintain type safety.
## Hook
For checking permissions in your components, you can use the `usePermix` hook. And to avoid importing the hook and Permix instance in every component, you can create a custom utility:
```tsx title="hooks/use-permissions.ts"
import { usePermix } from '@aminzoubaa/permix/solid'
import { permix } from '../lib/permix'
export function usePermissions() {
return usePermix(permix)
}
```
## Components
If you prefer using components, you can import the `createComponents` function from `permix/solid` and create checking components:
```ts title="lib/permix.ts"
import { createComponents } from '@aminzoubaa/permix/solid'
// ...
export const { Check } = createComponents(permix)
```
And then you can use the `Check` component in your components:
```tsx title="page.tsx"
export default function Page() {
return (
Will show this if a user doesn't have permission} // Will show this if a user doesn't have permission
reverse // Will flip the logic of the permission check
>
Will show this if a user has permission
)
}
```
## Usage
Use the `usePermix` hook and checking components in your components:
```tsx title="page.tsx"
import { usePermix } from '@aminzoubaa/permix/solid'
import { permix } from './lib/permix'
import { Check } from './lib/permix-components'
export default function Page() {
const post = usePost()
const { check, isReady } = usePermix(permix)
const canEdit = () => check('post', 'edit', post)
return (
<>
{!isReady()
?
Loading permissions...
: (
{canEdit() ? (
) : (
You don't have permission to edit this post
)}
Can I create a post inside the Check component?
)
}
>
)
}
```
## Example
Start with the dedicated [Solid example](/docs/examples/solid) for a guided walkthrough, then open the source code from that page if you want to inspect the implementation details.
# tRPC
URL: https://aminzoubaa.github.io/permix/docs/integrations/trpc
Learn how to use Permix with tRPC
## Overview
Permix provides a middleware for tRPC that allows you to easily check permissions in your procedures. The middleware can be created using the `createPermix` function.
Before getting started with tRPC integration, make sure you've completed the initial setup steps in the [Quick Start](/docs/quick-start) guide.
If you are mounting tRPC inside Next.js route handlers, also read the [Next.js guide](/docs/integrations/next). The context should receive a fresh request-scoped Permix instance before `checkMiddleware()` runs.
## Setup
Here's a basic example of how to use the Permix middleware with tRPC:
```ts twoslash
import { initTRPC } from '@trpc/server'
import { createPermix } from '@aminzoubaa/permix/trpc'
interface Post {
id: string
authorId: string
title: string
content: string
}
interface Context {
user: {
id: string
role: string
}
}
// Initialize tRPC
const t = initTRPC.context().create()
// Create your Permix instance
const permix = createPermix<{
post: {
dataType: Post
action: 'create' | 'read' | 'update' | 'delete'
}
}>()
// Create a protected procedure with Permix
const protectedProcedure = t.procedure.use(({ ctx, next }) => {
// You can access ctx.user or other properties to determine permissions
const isAdmin = ctx.user.role === 'admin'
const p = permix.setup({
post: {
create: true,
read: true,
update: isAdmin,
delete: isAdmin
}
})
return next({
ctx: {
permix: p,
},
})
})
```
The middleware preserves the context and input types from your tRPC procedures, ensuring end-to-end type safety in your API.
## Checking Permissions
Use the `checkMiddleware` function in your tRPC procedures to check permissions:
```ts
const router = t.router({
createPost: protectedProcedure
.use(permix.checkMiddleware('post', 'create'))
.mutation(({ input }) => {
// Create post logic here
return { success: true }
}),
updatePost: protectedProcedure
.use(permix.checkMiddleware('post', ['read', 'update']))
.mutation(({ input }) => {
// Update post logic here
return { success: true }
}),
deletePost: protectedProcedure
.use(permix.checkMiddleware('post', 'delete'))
.mutation(({ input }) => {
// Delete post logic here
return { success: true }
}),
allAction: protectedProcedure
.use(permix.checkMiddleware('post', 'all'))
.query(() => {
// Admin-only action
return { success: true }
}),
anyAction: protectedProcedure
.use(permix.checkMiddleware('post', 'any'))
.query(() => {
// Any action
return { success: true }
})
})
```
## Accessing Permix in Procedures
Permix is automatically added to your tRPC context, so you can access it directly:
```ts
const router = t.router({
getPosts: protectedProcedure
.query(({ ctx }) => {
// Check permissions manually
if (ctx.permix.check('post', 'read')) {
// User has permission to read posts
return getAllPosts()
}
// If not explicitly blocked by middleware, you can handle permission failures here
throw new TRPCError({
code: 'FORBIDDEN',
message: 'You do not have permission to read posts'
})
})
})
```
The `ctx.permix` object contains:
* `check` - Synchronously check one permission tuple
* `checkSome` - Allow access if any listed tuple matches
* `checkEvery` - Require every listed tuple to match
* `dehydrate` - Serialize the current request-scoped snapshot when you need to send it to the client
## Using Templates
Permix provides a template helper to create reusable permission rule sets:
```ts
// Create a template for admin permissions
const adminTemplate = permix.template({
post: {
create: true,
read: true,
update: true,
delete: true
}
})
// Create a template for regular user permissions
const userTemplate = permix.template({
post: {
create: true,
read: true,
update: false,
delete: false
}
})
// Use templates in your middleware
const protectedProcedure = t.procedure.use(({ ctx, next }) => {
const p = permix.setup(
ctx.user.role === 'admin'
? adminTemplate
: userTemplate
)
return next({
ctx: {
permix: p,
},
})
})
```
## Custom Error Handling
By default, the middleware throws a `TRPCError` with code `FORBIDDEN`. You can customize this behavior by providing a `forbiddenError` option:
### Static Error
```ts
const permix = createPermix({
forbiddenError: new TRPCError({
code: 'FORBIDDEN',
message: 'Custom forbidden message',
})
})
```
### Dynamic Error
You can also provide a function that returns different errors based on the entity and actions:
```ts
const permix = createPermix({
forbiddenError: ({ entity, actions, ctx }) => {
if (entity === 'post' && actions.includes('create')) {
return new TRPCError({
code: 'FORBIDDEN',
message: `User ${ctx.user.id} doesn't have permission to ${actions.join('/')} a ${entity}`,
})
}
return new TRPCError({
code: 'FORBIDDEN',
message: 'You do not have permission to perform this action',
})
},
})
```
The `forbiddenError` handler receives:
* `ctx`: Your tRPC context object
* `entity`: The entity that was checked
* `actions`: Array of actions that were checked
## Advanced Usage
### Async Permission Rules
You can use async functions in your permission setup:
```ts
const protectedProcedure = t.procedure.use(async ({ ctx, next }) => {
// Fetch user permissions from database
const userPermissions = await getUserPermissions(ctx.user.id)
const p = permix.setup({
post: {
create: userPermissions.canCreatePosts,
read: userPermissions.canReadPosts,
update: userPermissions.canUpdatePosts,
delete: userPermissions.canDeletePosts
}
})
return next({
ctx: {
permix: p,
},
})
})
```
### Dynamic Data-Based Permissions
You can check permissions based on the specific data being accessed:
```ts
const router = t.router({
updatePost: protectedProcedure
.input(z.object({ postId: z.string() }))
.mutation(async ({ input, ctx }) => {
const post = await getPostById(input.postId)
// Check if user can update this specific post
if (ctx.permix.check('post', 'update', post)) {
// Update post logic
return { success: true }
}
throw new TRPCError({
code: 'FORBIDDEN',
message: 'You cannot update this specific post'
})
})
})
```
# Vue
URL: https://aminzoubaa.github.io/permix/docs/integrations/vue
Learn how to use Permix with Vue applications
## Overview
Permix provides official Vue integration through the `permixPlugin` and `usePermix` composable. This allows you to manage permissions reactively in your Vue app.
Before getting started with Vue integration, make sure you've completed the initial setup steps in the [Quick Start](/docs/quick-start) guide.
## Setup
First, install the Vue plugin in your application:
```ts title="main.ts"
import { createApp } from 'vue'
import { permixPlugin } from '@aminzoubaa/permix/vue'
import { permix } from './lib/permix'
import App from './App.vue'
const app = createApp(App)
app.use(permixPlugin, { permix })
app.mount('#app')
```
Remember to always use the same Permix instance here and in the `usePermix` composable to maintain type safety.
## Composable
For checking permissions in your components, you can use the `usePermix` composable. And to avoid importing the composable and Permix instance in every component, you can create a custom composable:
```ts title="composables/use-permissions.ts"
import { usePermix } from '@aminzoubaa/permix/vue'
import { permix } from './lib/permix'
export function usePermissions() {
return usePermix(permix)
}
```
## Components
If you prefer using components, you can import the `createComponents` function from `permix/vue` and create checking components:
```ts title="lib/permix.ts"
import { createComponents } from '@aminzoubaa/permix/vue'
// ...
export const { Check } = createComponents(permix)
```
And then you can use the `Check` component in your templates:
```vue title="page.vue"
Will show this if a user have permission
Will show this if a user doesn't have permission
```
## Usage
Use the `usePermix` composable in your components to check permissions:
```vue title="page.vue"
Loading permissions...
You don't have permission to edit this post
Can I edit this post?
You don't have permission to edit this post
```
## Example
Start with the dedicated [Vue example](/docs/examples/vue) for a guided walkthrough, then open the source code from that page if you want to inspect the implementation details.