Build complex permission rules using fluent builder pattern or functional DSL approach for flexible ability configuration.
Fluent interface for building abilities with can/cannot methods and rule accumulation.
/**
* Fluent interface for building abilities with rule accumulation
*/
class AbilityBuilder<T extends AnyAbility> {
/** Array of accumulated rules */
rules: RawRuleOf<T>[];
/** Add permission rules (allow actions) */
can: DefineRule<T>;
/** Add restriction rules (deny actions) */
cannot: DefineRule<T>;
/**
* Build the final ability instance with accumulated rules
* @param options - Options for the ability instance
* @returns New ability instance with all accumulated rules
*/
build(options?: AbilityOptionsOf<T>): T;
}
/**
* Rule definition function type used by can/cannot methods
*/
type DefineRule<T extends AnyAbility> = (
action: Parameters<T['can']>[0],
subject?: Parameters<T['can']>[1],
conditionsOrFields?: any,
fields?: string | string[]
) => void;Usage Examples:
import { AbilityBuilder, createMongoAbility } from "@casl/ability";
// Basic builder usage
const { can, cannot, build } = new AbilityBuilder(createMongoAbility);
// Add permission rules
can('read', 'Post');
can('create', 'Post');
can('update', 'Post', { authorId: 'user123' });
can('read', 'User', ['name', 'email']); // Field-level permissions
// Add restriction rules
cannot('delete', 'Post', { published: true });
cannot('update', 'User', { role: 'admin' });
// Build the ability
const ability = build();
console.log(ability.can('read', 'Post')); // true
console.log(ability.can('delete', { __type: 'Post', published: true })); // false
// Multiple builders for different contexts
const adminBuilder = new AbilityBuilder(createMongoAbility);
adminBuilder.can('manage', 'all'); // Admin can do everything
const adminAbility = adminBuilder.build();
const userBuilder = new AbilityBuilder(createMongoAbility);
userBuilder.can('read', 'Post');
userBuilder.can('create', 'Comment');
userBuilder.cannot('delete', 'Comment', { authorId: { $ne: 'user123' } });
const userAbility = userBuilder.build();
// Builder with custom options
const customBuilder = new AbilityBuilder(createMongoAbility);
customBuilder.can('read', 'Document');
const customAbility = customBuilder.build({
detectSubjectType: (subject) => subject?.type || subject?.constructor?.name,
resolveAction: (action) => action === 'manage' ? ['create', 'read', 'update', 'delete'] : action
});Common patterns for defining rules with conditions, fields, and complex logic.
/**
* Raw rule structure for ability definitions
*/
interface RawRule<A extends Abilities, C = AnyObject> {
/** Action or actions this rule applies to */
action: A extends readonly [infer Action, any] ? Action : A;
/** Subject or subjects this rule applies to (optional for claim-based rules) */
subject?: A extends readonly [any, infer S] ? S : string;
/** Conditions that must be met for rule to apply */
conditions?: C;
/** Field restrictions (allow only these fields) */
fields?: string | string[];
/** Whether this is a denial rule */
inverted?: boolean;
/** Reason for denial (for inverted rules) */
reason?: string;
}Usage Examples:
import { AbilityBuilder, createMongoAbility } from "@casl/ability";
const { can, cannot, build } = new AbilityBuilder(createMongoAbility);
// Basic permissions
can('read', 'Article');
can('create', 'Article');
// Conditional permissions with MongoDB-style queries
can('update', 'Article', { authorId: 'user123' }); // Only own articles
can('publish', 'Article', {
authorId: 'user123',
status: { $in: ['draft', 'review'] }
}); // Complex conditions
// Field-level permissions
can('read', 'User', ['name', 'email', 'avatar']); // Only specific fields
can('update', 'User', { id: 'user123' }, ['name', 'email']); // Conditional field access
// Denial rules with reasons
cannot('delete', 'Article', { published: true }, undefined, 'Published articles cannot be deleted');
cannot('update', 'User', { role: 'admin' }, undefined, 'Admin users cannot be modified');
// Multiple actions
can(['read', 'create'], 'Comment');
cannot(['update', 'delete'], 'Comment', { locked: true });
// Multiple subjects
can('read', ['Article', 'Comment', 'User']);
// Claim-based rules (no subject)
can('login'); // Global permission
cannot('access_admin', undefined, undefined, 'Insufficient privileges');
// Complex nested conditions
can('moderate', 'Comment', {
$or: [
{ 'article.authorId': 'user123' }, // Own article comments
{ 'assignedModerators': { $in: ['user123'] } } // Assigned moderator
]
});
const ability = build();Build rules dynamically based on runtime conditions like user roles or context.
/**
* Helper type for conditional rule building
*/
type ConditionalRuleBuilder<T extends AnyAbility> = {
when: (condition: boolean) => {
can: DefineRule<T>;
cannot: DefineRule<T>;
};
};Usage Examples:
import { AbilityBuilder, createMongoAbility } from "@casl/ability";
// Dynamic rules based on user roles
function buildUserAbility(user: { id: string; roles: string[]; department: string }) {
const { can, cannot, build } = new AbilityBuilder(createMongoAbility);
// Base permissions for all users
can('read', 'Profile', { userId: user.id });
can('update', 'Profile', { userId: user.id });
// Role-based permissions
if (user.roles.includes('admin')) {
can('manage', 'all'); // Admins can do everything
} else {
// Regular user permissions
can('read', 'Article');
can('create', 'Comment');
can('update', 'Comment', { authorId: user.id });
cannot('delete', 'Comment', { replies: { $gt: 0 } }, undefined, 'Cannot delete comments with replies');
}
if (user.roles.includes('editor')) {
can(['create', 'update', 'publish'], 'Article');
can('moderate', 'Comment');
}
if (user.roles.includes('moderator')) {
can(['update', 'delete'], 'Comment');
can('ban', 'User', { role: { $ne: 'admin' } });
}
// Department-based permissions
switch (user.department) {
case 'engineering':
can('deploy', 'Application');
can('access', 'ServerLogs');
break;
case 'marketing':
can(['create', 'update'], 'Campaign');
can('view', 'Analytics');
break;
case 'hr':
can('manage', 'Employee');
can('view', 'Payroll');
break;
}
return build();
}
// Usage
const adminUser = { id: 'admin1', roles: ['admin'], department: 'engineering' };
const editorUser = { id: 'editor1', roles: ['editor'], department: 'marketing' };
const regularUser = { id: 'user1', roles: ['user'], department: 'engineering' };
const adminAbility = buildUserAbility(adminUser);
const editorAbility = buildUserAbility(editorUser);
const userAbility = buildUserAbility(regularUser);
console.log(adminAbility.can('delete', 'User')); // true (admin can manage all)
console.log(editorAbility.can('publish', 'Article')); // true (editor role)
console.log(userAbility.can('deploy', 'Application')); // true (engineering department)
// Conditional helper function
function createConditionalBuilder<T extends AnyAbility>(builder: AbilityBuilder<T>) {
return {
when: (condition: boolean) => ({
can: condition ? builder.can : () => {},
cannot: condition ? builder.cannot : () => {}
})
};
}
// Usage with conditional helper
function buildFeatureAbility(user: { id: string; features: string[] }) {
const builder = new AbilityBuilder(createMongoAbility);
const { can, cannot } = builder;
const conditional = createConditionalBuilder(builder);
// Always allowed
can('read', 'Profile');
// Feature-based permissions
const hasFeature = (feature: string) => user.features.includes(feature);
conditional.when(hasFeature('beta_editor')).can('use', 'BetaEditor');
conditional.when(hasFeature('advanced_analytics')).can('view', 'AdvancedAnalytics');
conditional.when(hasFeature('team_collaboration')).can('invite', 'TeamMember');
conditional.when(!hasFeature('premium')).cannot('export', 'Report', undefined, 'Premium feature required');
return builder.build();
}Handle rule conflicts and priorities when multiple rules apply to the same action/subject.
/**
* Rule priority is determined by order - later rules override earlier ones
* More specific rules (with conditions/fields) take precedence over general rules
*/
interface RulePriorityBehavior {
/** Later rules in the array have higher priority */
order: 'later-wins';
/** Rules with conditions have higher priority than rules without */
specificity: 'conditions-win';
/** Field-specific rules have higher priority than general rules */
fieldSpecificity: 'field-specific-wins';
}Usage Examples:
import { AbilityBuilder, createMongoAbility } from "@casl/ability";
const { can, cannot, build } = new AbilityBuilder(createMongoAbility);
// Rule priority by order (later wins)
can('update', 'Article'); // General rule
cannot('update', 'Article', { published: true }); // More specific rule wins
// The specific rule prevents updating published articles
// even though the general rule allows updating articles
// Field-level priority
can('read', 'User'); // Can read all user fields
cannot('read', 'User', undefined, ['password', 'socialSecurityNumber']); // Cannot read sensitive fields
// Specificity example
can('delete', 'Comment'); // General permission
cannot('delete', 'Comment', { hasReplies: true }); // Specific restriction
can('delete', 'Comment', { authorId: 'user123', hasReplies: true }); // Even more specific override
const ability = build();
// Testing priority resolution
const publishedArticle = { __type: 'Article', published: true };
const draftArticle = { __type: 'Article', published: false };
console.log(ability.can('update', draftArticle)); // true (general rule applies)
console.log(ability.can('update', publishedArticle)); // false (specific rule prevents)
const commentWithReplies = {
__type: 'Comment',
authorId: 'user123',
hasReplies: true
};
const otherCommentWithReplies = {
__type: 'Comment',
authorId: 'other',
hasReplies: true
};
console.log(ability.can('delete', commentWithReplies)); // true (most specific rule allows)
console.log(ability.can('delete', otherCommentWithReplies)); // false (middle rule prevents)
// Complex priority example
function buildComplexAbility() {
const { can, cannot, build } = new AbilityBuilder(createMongoAbility);
// Base permissions
can('read', 'Document');
// Department restrictions
cannot('read', 'Document', { department: 'hr' });
cannot('read', 'Document', { department: 'finance' });
// Role-based overrides
can('read', 'Document', { department: 'hr', assignedUsers: { $in: ['user123'] } });
can('read', 'Document', { department: 'finance' }, ['title', 'summary']); // Limited fields
// Security level restrictions
cannot('read', 'Document', { securityLevel: 'confidential' });
can('read', 'Document', {
securityLevel: 'confidential',
clearanceLevel: { $gte: 3 }
});
return build();
}