granter

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 returns boolean or Promise<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 true if 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 true only 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 true if 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 true only 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 context
  • permissions - 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 context
  • resource - (Optional) Resource to check
  • error - (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 context
  • resources - 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 context
  • resource - (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