Core permission verification functionality with support for conditions, field-level access control, and rule matching.
Check if specific actions are allowed or forbidden on subjects.
/**
* Parameters for permission checking methods
*/
type CanParameters<A extends Abilities> =
| [action: ActionFrom<A>]
| [action: ActionFrom<A>, subject: SubjectFrom<A>]
| [action: ActionFrom<A>, subject: SubjectFrom<A>, field: string];
/**
* Check if action is allowed
* @param action - Action to check (e.g., 'read', 'create', 'update', 'delete')
* @param subject - Subject to check against (optional for claim-based rules)
* @param field - Specific field to check (optional for field-level permissions)
* @returns true if action is allowed, false otherwise
*/
can(...args: CanParameters<A>): boolean;
/**
* Check if action is forbidden (inverse of can)
* @param action - Action to check
* @param subject - Subject to check against (optional)
* @param field - Specific field to check (optional)
* @returns true if action is forbidden, false otherwise
*/
cannot(...args: CanParameters<A>): boolean;Usage Examples:
import { createMongoAbility } from "@casl/ability";
const ability = createMongoAbility([
{ action: 'read', subject: 'Post' },
{ action: 'update', subject: 'Post', conditions: { authorId: 'user123' } },
{ action: 'read', subject: 'User', fields: ['name', 'email'] }
]);
// Basic permission checks
console.log(ability.can('read', 'Post')); // true
console.log(ability.can('delete', 'Post')); // false
console.log(ability.cannot('delete', 'Post')); // true
// Conditional permission checks
const ownPost = { __type: 'Post', id: 1, authorId: 'user123', title: 'My Post' };
const otherPost = { __type: 'Post', id: 2, authorId: 'other', title: 'Other Post' };
console.log(ability.can('update', ownPost)); // true
console.log(ability.can('update', otherPost)); // false
// Field-level permission checks
console.log(ability.can('read', 'User', 'name')); // true
console.log(ability.can('read', 'User', 'password')); // false
// Subject-only checks (claim-based)
const claimAbility = createMongoAbility([
{ action: 'read' }, // No subject specified
{ action: 'create' }
]);
console.log(claimAbility.can('read')); // true
console.log(claimAbility.can('delete')); // falseGet detailed information about rules that match specific conditions.
/**
* Get the most relevant rule for the given parameters
* @param action - Action to find rule for
* @param subject - Subject to find rule for (optional)
* @param field - Field to find rule for (optional)
* @returns The most relevant rule or null if no matching rule
*/
relevantRuleFor(...args: CanParameters<A>): Rule<A, Conditions> | null;
/**
* Get all rules that match the given criteria
* @param action - Action to match
* @param subjectType - Subject type to match (optional)
* @param field - Field to match (optional)
* @returns Array of matching rules
*/
rulesFor(action: string, subjectType?: string, field?: string): Rule<A, Conditions>[];
/**
* Get all possible rules for an action and subject type (before condition matching)
* @param action - Action to get rules for
* @param subjectType - Subject type to get rules for
* @returns Array of potentially matching rules
*/
possibleRulesFor(action: string, subjectType: string): Rule<A, Conditions>[];
/**
* Get all actions allowed for a subject type
* @param subjectType - Subject type to get actions for
* @returns Array of allowed action strings
*/
actionsFor(subjectType: string): string[];Usage Examples:
import { createMongoAbility } from "@casl/ability";
const ability = createMongoAbility([
{ action: 'read', subject: 'Post' },
{ action: 'update', subject: 'Post', conditions: { authorId: 'user123' } },
{ action: 'delete', subject: 'Post', inverted: true, conditions: { published: true } },
{ action: 'read', subject: 'User', fields: ['name', 'email'] }
]);
// Get relevant rule
const updateRule = ability.relevantRuleFor('update', { __type: 'Post', authorId: 'user123' });
console.log(updateRule?.conditions); // { authorId: 'user123' }
console.log(updateRule?.inverted); // false
const deleteRule = ability.relevantRuleFor('delete', { __type: 'Post', published: true });
console.log(deleteRule?.inverted); // true (cannot delete)
// Get all matching rules
const postRules = ability.rulesFor('read', 'Post');
console.log(postRules.length); // 1
const userFieldRules = ability.rulesFor('read', 'User');
console.log(userFieldRules[0].fields); // ['name', 'email']
// Get possible rules (before condition matching)
const possibleUpdateRules = ability.possibleRulesFor('update', 'Post');
console.log(possibleUpdateRules.length); // 1
// Get allowed actions
const postActions = ability.actionsFor('Post');
console.log(postActions); // ['read', 'update'] (delete not included due to inverted rule)
const userActions = ability.actionsFor('User');
console.log(userActions); // ['read']Information about individual rule objects returned by rule inspection methods.
/**
* Compiled rule with matching capabilities
*/
interface Rule<A extends Abilities, Conditions> {
/** Action(s) this rule applies to */
readonly action: string | string[];
/** Subject(s) this rule applies to */
readonly subject?: string | string[];
/** Whether this is a denial rule (cannot) */
readonly inverted: boolean;
/** Conditions that must be met for rule to apply */
readonly conditions?: Conditions;
/** Field restrictions for this rule */
readonly fields?: string[];
/** Reason for denial (for inverted rules) */
readonly reason?: string;
/** Rule priority for conflict resolution */
readonly priority: number;
/** Original raw rule this was compiled from */
readonly origin: RawRule<A, Conditions>;
/** Parsed condition AST (computed property) */
readonly ast?: Condition;
/**
* Check if an object matches this rule's conditions
* @param object - Object to test against conditions
* @returns true if object matches the conditions
*/
matchesConditions(object: any): boolean;
/**
* Check if a field matches this rule's field restrictions
* @param field - Field name to check
* @returns true if field is allowed by this rule
*/
matchesField(field: string): boolean;
}Usage Examples:
import { createMongoAbility } from "@casl/ability";
const ability = createMongoAbility([
{
action: 'update',
subject: 'Post',
conditions: { authorId: 'user123', status: { $ne: 'published' } },
fields: ['title', 'content']
}
]);
const rule = ability.relevantRuleFor('update', 'Post');
if (rule) {
console.log(rule.action); // 'update'
console.log(rule.subject); // 'Post'
console.log(rule.inverted); // false
console.log(rule.conditions); // { authorId: 'user123', status: { $ne: 'published' } }
console.log(rule.fields); // ['title', 'content']
// Test condition matching
const draftPost = { authorId: 'user123', status: 'draft', title: 'Test' };
const publishedPost = { authorId: 'user123', status: 'published', title: 'Test' };
console.log(rule.matchesConditions(draftPost)); // true
console.log(rule.matchesConditions(publishedPost)); // false
// Test field matching
console.log(rule.matchesField('title')); // true
console.log(rule.matchesField('author')); // false
}Utilities for working with subject types in permission checks.
/**
* Detect the type of a subject object
* @param object - Object to detect type for
* @returns String representation of the subject type
*/
function detectSubjectType(object?: any): string;
/**
* Utility type for objects with explicit type annotation
*/
type ForcedSubject<T> = { __type: T };
/**
* Attach explicit type to subject objects
* @param type - The type to attach
* @param object - The object to attach type to
* @returns Object with type annotation
*/
function subject<T extends SubjectType, U extends Record<PropertyKey, any>>(
type: T,
object: U
): U & ForcedSubject<T>;Usage Examples:
import { createMongoAbility, subject, detectSubjectType } from "@casl/ability";
const ability = createMongoAbility([
{ action: 'read', subject: 'Article' },
{ action: 'update', subject: 'BlogPost', conditions: { authorId: 'user123' } }
]);
// Using class constructors for type detection
class Article {
constructor(public title: string, public content: string) {}
}
class BlogPost {
constructor(public title: string, public authorId: string) {}
}
const article = new Article('Test Article', 'Content');
const blogPost = new BlogPost('Test Post', 'user123');
console.log(detectSubjectType(article)); // 'Article'
console.log(detectSubjectType(blogPost)); // 'BlogPost'
console.log(ability.can('read', article)); // true
console.log(ability.can('update', blogPost)); // true
// Using explicit type annotation
const plainObject = { title: 'Plain Object', authorId: 'user123' };
const typedObject = subject('BlogPost', plainObject);
console.log(detectSubjectType(plainObject)); // 'Object'
console.log(detectSubjectType(typedObject)); // 'BlogPost'
console.log(ability.can('update', plainObject)); // false (wrong type)
console.log(ability.can('update', typedObject)); // true (correct type and conditions)
// Using __type property directly
const manuallyTyped = {
__type: 'BlogPost',
title: 'Manual Type',
authorId: 'user123'
};
console.log(detectSubjectType(manuallyTyped)); // 'BlogPost'
console.log(ability.can('update', manuallyTyped)); // true