Permit Usage Guide
Complete guide to installing and using Permit in your projects.
💡 API Reference
This guide covers API usage and basic patterns. For complete application examples, see Examples.
Table of Contents
Installation
pnpm add @vielzeug/permitnpm install @vielzeug/permityarn add @vielzeug/permitImport
import { Permit } from '@vielzeug/permit';
// Optional: Import types and constants
import type {
BaseUser,
PermissionAction,
PermissionCheck,
PermissionMap,
ResourcePermissions,
RolesWithPermissions,
} from '@vielzeug/permit';
import { WILDCARD, ANONYMOUS } from '@vielzeug/permit';Basic Usage
Registering Permissions
// Static permissions (always true/false)
Permit.register('admin', 'posts', {
read: true,
create: true,
update: true,
delete: true,
});
Permit.register('viewer', 'posts', {
read: true,
create: false,
update: false,
delete: false,
});
// Permissions are normalized (case-insensitive, trimmed)
Permit.register('Editor', 'Posts', { read: true });
// Same as: Permit.register('editor', 'posts', { read: true });Checking Permissions
const user = { id: '123', roles: ['viewer'] };
// Check if user can view posts
const canView = Permit.check(user, 'posts', 'read'); // true
// Check if user can delete posts
const canDelete = Permit.check(user, 'posts', 'delete'); // false
// Role and resource names are normalized
const userWithCaps = { id: '456', roles: ['EDITOR'] };
Permit.check(userWithCaps, 'POSTS', 'read'); // true (normalized matching)Dynamic Permissions
// Function-based permissions for context-aware checks
Permit.register('author', 'posts', {
update: (user, post) => user.id === post.authorId,
delete: (user, post) => user.id === post.authorId && post.status === 'draft',
});
const user = { id: '123', roles: ['author'] };
const post = { id: 'p1', authorId: '123', status: 'draft' };
// Must provide data for dynamic permissions
const canUpdate = Permit.check(user, 'posts', 'update', post); // true
const canDelete = Permit.check(user, 'posts', 'delete', post); // true
// Without data, function permissions return false
Permit.check(user, 'posts', 'update'); // false (no data provided)Advanced Features
Wildcards
Use the exported WILDCARD constant for permissions that apply to all resources or roles:
import { Permit, WILDCARD } from '@vielzeug/permit';
// Admin has all permissions on all resources
Permit.register('admin', WILDCARD, {
read: true,
create: true,
update: true,
delete: true,
});
// All roles can view posts
Permit.register(WILDCARD, 'posts', {
read: true,
});
// Specific permissions override wildcards
Permit.register('admin', WILDCARD, { read: true });
Permit.register('admin', 'secrets', { read: false }); // Specific winsAnonymous Users
Use the ANONYMOUS constant for unauthenticated users:
import { Permit, ANONYMOUS } from '@vielzeug/permit';
// Public read access for unauthenticated users
Permit.register(ANONYMOUS, 'posts', { read: true });
// Malformed users are treated as ANONYMOUS + WILDCARD
const malformedUser = null;
Permit.check(malformedUser, 'posts', 'read'); // true (has ANONYMOUS role)Security Note
Malformed users (missing id or roles) automatically receive both ANONYMOUS and WILDCARD roles. Ensure wildcard permissions are intended for public access.
Multiple Roles
Users can have multiple roles, and permissions are additive (first-match-wins):
Permit.register('viewer', 'posts', { read: true });
Permit.register('creator', 'posts', { create: true });
const user = { id: '1', roles: ['viewer', 'creator'] };
// User has permissions from both roles
Permit.check(user, 'posts', 'read'); // true
Permit.check(user, 'posts', 'create'); // trueSetting Permissions
Use set() to replace or merge permissions:
// Merge with existing (default)
Permit.set('editor', 'posts', { read: true, create: true });
// Replace completely
Permit.set('editor', 'posts', { read: true }, true); // Only read remainsUnregistering Permissions
Remove permissions when no longer needed:
// Remove specific action
Permit.unregister('editor', 'posts', 'delete');
// Remove all actions for a resource
Permit.unregister('editor', 'posts');
// Automatically cleans up empty role/resource entriesChecking User Roles
Use hasRole() helper for role checks:
const user = { id: '1', roles: ['Admin', 'Editor'] };
// Normalized comparison (case-insensitive)
Permit.hasRole(user, 'admin'); // true
Permit.hasRole(user, 'EDITOR'); // true
Permit.hasRole(user, 'moderator'); // false
// For malformed users, only ANONYMOUS role returns true
const malformed = null;
Permit.hasRole(malformed, ANONYMOUS); // true
Permit.hasRole(malformed, 'admin'); // falseMerging Permissions
Registering permissions for the same role/resource merges them:
Permit.register('editor', 'posts', { read: true, create: true });
Permit.register('editor', 'posts', { update: true }); // Adds to existing
// Editor now has: read, create, and updateTypeScript Generics
Use generics for better type safety:
interface User extends BaseUser {
email: string;
department: string;
}
interface Post {
id: string;
authorId: string;
department: string;
}
Permit.register<User, Post>('manager', 'posts', {
update: (user, post) => {
// Full type inference
return user.department === post.department;
},
});Clearing Permissions
// Clear all registered permissions
Permit.clear();
// Useful for testing or re-initializationInspecting Permissions
// Get deep copy of all registered permissions
const allPermissions = Permit.roles;
// Iterate over roles
for (const [role, resources] of allPermissions) {
console.log(`Role: ${role}`);
for (const [resource, actions] of resources) {
console.log(` Resource: ${resource}`, actions);
}
}Permission Patterns
Role-Based Access Control (RBAC)
// Define roles with specific permissions
Permit.register('admin', 'users', {
read: true,
create: true,
update: true,
delete: true,
});
Permit.register('moderator', 'users', {
read: true,
update: true,
});
Permit.register('user', 'users', {
read: true,
});Resource Ownership
// Users can only modify their own resources
Permit.register('user', 'profile', {
read: true,
update: (user, profile) => user.id === profile.userId,
delete: (user, profile) => user.id === profile.userId,
});Status-Based Permissions
// Permissions depend on resource status
Permit.register('editor', 'articles', {
update: (user, article) => {
return article.status === 'draft' || article.status === 'review';
},
delete: (user, article) => {
return article.status === 'draft';
},
});Hierarchical Permissions
// Combine role levels with resource ownership
Permit.register('admin', 'documents', {
read: true,
create: true,
update: true,
delete: true,
});
Permit.register('manager', 'documents', {
read: true,
create: true,
update: (user, doc) => doc.department === user.department,
delete: (user, doc) => doc.department === user.department,
});
Permit.register('employee', 'documents', {
read: (user, doc) => doc.department === user.department,
create: true,
update: (user, doc) => doc.authorId === user.id,
delete: (user, doc) => doc.authorId === user.id,
});Integration Patterns
With Authentication
// After user login
function onLogin(user) {
// Load user-specific permissions
const permissions = await fetchUserPermissions(user.id);
permissions.forEach((perm) => {
Permit.register(perm.role, perm.resource, perm.actions);
});
}With React
import { Permit } from '@vielzeug/permit';
import { useAuth } from './auth';
function usePermission(resource: string, action: string, data?: any) {
const { user } = useAuth();
return Permit.check(user, resource, action, data);
}
// Usage
function DeleteButton({ post }) {
const canDelete = usePermission('posts', 'delete', post);
if (!canDelete) return null;
return <button onClick={() => deletePost(post)}>Delete</button>;
}With Express
import { Permit } from '@vielzeug/permit';
function authorize(resource: string, action: string) {
return (req, res, next) => {
if (!Permit.check(req.user, resource, action, req.body)) {
return res.status(403).json({ error: 'Permission denied' });
}
next();
};
}
// Usage
app.delete('/api/posts/:id', authorize('posts', 'delete'), async (req, res) => {
// Handle deletion
});With Vue
// composable
import { computed } from 'vue';
import { Permit } from '@vielzeug/permit';
import { useAuth } from './auth';
export function usePermission(resource: string, action: string, data?: any) {
const { user } = useAuth();
return computed(() => {
return Permit.check(user.value, resource, action, data?.value);
});
}Best Practices
- Register permissions early: Register all permissions during app initialization
- Use TypeScript: Leverage generics for type-safe permission functions
- Clear in tests: Always call
Permit.clear()before each test - Validate user structure: Ensure user has
idandrolesproperties - Provide data for dynamic permissions: Function-based permissions need context
- Use constants: Import and use
WILDCARDinstead of string literals - Document permissions: Comment why specific roles have certain permissions
- Audit regularly: Use
Permit.rolesto inspect registered permissions
Common Patterns
Loading Permissions from Database
async function initializePermissions() {
const permissions = await db.permissions.findAll();
for (const perm of permissions) {
Permit.register(perm.role, perm.resource, {
read: perm.canView,
create: perm.canCreate,
update: perm.canUpdate,
delete: perm.canDelete,
});
}
}Environment-Specific Permissions
if (process.env.NODE_ENV === 'development') {
// Dev-only permissions
Permit.register('developer', WILDCARD, {
read: true,
create: true,
update: true,
delete: true,
});
}Caching Permission Checks
// For expensive permission checks
const permissionCache = new Map<string, boolean>();
function checkWithCache(user, resource, action, data?) {
const key = `${user.id}-${resource}-${action}`;
if (permissionCache.has(key)) {
return permissionCache.get(key);
}
const result = Permit.check(user, resource, action, data);
permissionCache.set(key, result);
return result;
}Next Steps
💡 Continue Learning
- API Reference – Complete API documentation
- Examples – Practical code examples
- Interactive REPL – Try it in your browser