API Reference
Complete API reference for all granter functions and types
Core Functions
permission()
Create a permission check function.
function permission<TContext, TResource = undefined>(
name: string,
check: PermissionCheck<TContext, TResource>
): Permission<TContext, TResource>
Parameters:
name- Human-readable permission name (used in.explain()output)check- Function that returnsbooleanorPromise<boolean>
Returns: A callable Permission object with methods
Example:
// Context-only permission
const isAdmin = permission('isAdmin', (ctx: AppContext) => {
return ctx.user.role === 'admin';
});
// Resource-based permission
const isPostOwner = permission('isPostOwner', (ctx: AppContext, post: Post) => {
return post.authorId === ctx.user.id;
});
// Async permission
const hasMembership = permission('hasMembership', async (ctx: AppContext, orgId: string) => {
const membership = await ctx.db.membership.findFirst({
where: { userId: ctx.user.id, organizationId: orgId }
});
return !!membership;
});
or()
Combine permissions with OR logic (any permission must pass).
function or<TContext, TResource>(
...permissions: Permission<TContext, TResource>[]
): Permission<TContext, TResource>
Behavior:
- Runs permissions sequentially
- Short-circuits on first
true(stops checking remaining permissions) - Returns
trueif any permission passes
Example:
const canEdit = or(isPostOwner, isAdmin, isModerator);
// Stops at first true:
// 1. Check isPostOwner → false
// 2. Check isAdmin → true (stop here, return true)
// 3. isModerator never runs
and()
Combine permissions with AND logic (all permissions must pass).
function and<TContext, TResource>(
...permissions: Permission<TContext, TResource>[]
): Permission<TContext, TResource>
Behavior:
- Runs permissions sequentially
- Short-circuits on first
false(stops checking remaining permissions) - Returns
trueonly if all permissions pass
Example:
const canPublish = and(isAuthenticated, isVerified, isPostOwner);
// Stops at first false:
// 1. Check isAuthenticated → true
// 2. Check isVerified → false (stop here, return false)
// 3. isPostOwner never runs
not()
Invert a permission's result.
function not<TContext, TResource>(
permission: Permission<TContext, TResource>
): Permission<TContext, TResource>
Example:
const isBanned = permission('isBanned', (ctx) => ctx.user.isBanned);
const isNotBanned = not(isBanned);
const canComment = and(isAuthenticated, not(isBanned));
orParallel()
Parallel OR operator for DataLoader batching.
function orParallel<TContext, TResource>(
...permissions: Permission<TContext, TResource>[]
): Permission<TContext, TResource>
Behavior:
- Runs permissions in parallel using
Promise.all() - No short-circuit (all permissions run even if one passes)
- Returns
trueif any permission passes
Use when:
- Using DataLoader for database batching
- All checks are async I/O that can run concurrently
- Short-circuit optimization is less important than batching
Example:
const canView = orParallel(
isPublic,
isMember,
hasSharedLink
);
// All three checks run in parallel
andParallel()
Parallel AND operator for DataLoader batching.
function andParallel<TContext, TResource>(
...permissions: Permission<TContext, TResource>[]
): Permission<TContext, TResource>
Behavior:
- Runs permissions in parallel using
Promise.all() - No short-circuit (all permissions run even if one fails)
- Returns
trueonly if all permissions pass
Example:
const canEdit = andParallel(
isAuthenticated,
hasPermission,
isNotLocked
);
// All three checks run in parallel
withContext()
Bind context to permissions for cleaner code.
function withContext<TContext, T extends Record<string, Permission<TContext, any>>>(
ctx: TContext,
permissions: T
): BoundPermissions<TContext, T>
Parameters:
ctx- Application contextpermissions- Object of permissions to bind
Returns: Object with same keys, but permissions no longer require ctx argument
Example:
const abilities = withContext(ctx, {
canEdit,
canDelete,
isAdmin,
});
// No ctx needed
await abilities.canEdit(post);
await abilities.isAdmin();
// Methods still work
await abilities.canEdit.orThrow(post);
const editable = await abilities.canEdit.filter(posts);
Permission Methods
Every Permission object has these methods:
Direct Call
Check if action is allowed.
(ctx: TContext, resource?: TResource): Promise<boolean>
Example:
if (await canEdit(ctx, post)) {
// User can edit
}
.orThrow()
Require permission (throws if denied).
orThrow(
ctx: TContext,
resource?: TResource,
error?: string | Error | (() => Error)
): Promise<void>
Parameters:
ctx- Application contextresource- (Optional) Resource to checkerror- (Optional) Custom error message/instance/factory
Throws: ForbiddenError if permission denied
Example:
// Default error
await canEdit.orThrow(ctx, post);
// Custom message
await canEdit.orThrow(ctx, post, 'You cannot edit this post');
// Custom error
await canEdit.orThrow(ctx, post, new CustomError('Denied'));
// Error factory
await canEdit.orThrow(ctx, post, () => new CustomError('Denied'));
.filter()
Filter array to only allowed items.
filter(ctx: TContext, resources: TResource[]): Promise<TResource[]>
Parameters:
ctx- Application contextresources- Array of resources to filter
Returns: New array with only allowed items
Example:
const allPosts = await db.posts.findMany();
const editablePosts = await canEdit.filter(ctx, allPosts);
console.log(`Can edit ${editablePosts.length} of ${allPosts.length} posts`);
.explain()
Debug why permission passed/failed.
explain(ctx: TContext, resource?: TResource): Promise<ExplanationResult>
Parameters:
ctx- Application contextresource- (Optional) Resource to check
Returns: Detailed explanation of permission evaluation
Example:
const explanation = await canEdit.explain(ctx, post);
console.log(JSON.stringify(explanation, null, 2));
Types
Permission<TContext, TResource>
A callable permission function with methods.
type Permission<TContext, TResource = undefined> = {
(ctx: TContext, resource: TResource): Promise<boolean>;
name: string;
children: Permission<TContext, TResource>[];
orThrow(ctx: TContext, resource: TResource, error?: string | Error | (() => Error)): Promise<void>;
filter(ctx: TContext, resources: TResource[]): Promise<TResource[]>;
explain(ctx: TContext, resource: TResource): Promise<ExplanationResult>;
}
PermissionCheck<TContext, TResource>
The check function passed to permission().
type PermissionCheck<TContext, TResource = undefined> = (
ctx: TContext,
resource: TResource
) => boolean | Promise<boolean>
ExplanationResult
Result from .explain() method.
type ExplanationResult = {
name: string; // Permission name
value: boolean; // Result (true/false)
duration: number; // Time in milliseconds
operator?: 'OR' | 'AND' | 'NOT'; // Operator type (if composed)
children?: ExplanationResult[]; // Nested permissions (if composed)
}
Error Types
ForbiddenError
Thrown by .orThrow() when permission denied.
class ForbiddenError extends Error {
constructor(message?: string);
}
UnauthorizedError
For unauthenticated requests (you throw this manually).
class UnauthorizedError extends Error {
constructor(message?: string);
}
PermissionError
Base class for permission errors.
class PermissionError extends Error {
constructor(message?: string);
}
Next Steps
- TypeScript Guide - Advanced TypeScript patterns
- Testing - How to test permissions
- Examples - See API in action