Core functionality for creating and managing ability instances with rule-based permissions.
Creates ability instances with MongoDB-style condition matching, the recommended approach for most applications.
/**
* Creates an ability instance with MongoDB query condition matching
* Multiple overloads support different type constraints
*/
function createMongoAbility<
T extends AnyMongoAbility = MongoAbility
>(rules?: RawRuleOf<T>[], options?: AbilityOptionsOf<T>): T;
function createMongoAbility<
A extends AbilityTuple = AbilityTuple,
C extends MongoQuery = MongoQuery
>(rules?: RawRuleFrom<A, C>[], options?: AbilityOptions<A, C>): MongoAbility<A, C>;
// Options interface for ability configuration
interface AbilityOptions<A extends Abilities, C> extends RuleIndexOptions<A, C> {}
interface RuleIndexOptions<A extends Abilities, C> {
conditionsMatcher?: ConditionsMatcher<C>;
fieldMatcher?: FieldMatcher;
detectSubjectType?: (subject?: any) => string;
resolveAction?: (action: any) => string | string[];
}
// Matcher type definitions
type ConditionsMatcher<C> = (conditions: C) => MatchConditions;
type FieldMatcher = <T extends string>(fields: T[]) => MatchField<T>;
type MatchConditions<T extends {} = any> = {
(object: T): boolean;
ast?: any; // Condition from @ucast/mongo2js
};
type MatchField<T extends string> = (field: T) => boolean;
// MongoDB-specific types
type MongoQuery<T = AnyObject> = Record<string, any>;
type RawRuleFrom<A extends Abilities, C> = RawRule<ToAbilityTypes<A>, C>;
type ToAbilityTypes<T extends Abilities> = T extends AbilityTuple
? AbilityTupleType<T[0], ExtractSubjectType<T[1]>>
: Extract<T, string>;Usage Examples:
import { createMongoAbility } from "@casl/ability";
// Basic ability with rules
const ability = createMongoAbility([
{ action: 'read', subject: 'Post' },
{ action: 'update', subject: 'Post', conditions: { authorId: 'user123' } },
{ action: 'delete', subject: 'Post', inverted: true, conditions: { published: true } }
]);
// Empty ability, rules added later
const emptyAbility = createMongoAbility();
emptyAbility.update([
{ action: 'read', subject: 'Article' }
]);
// Custom options
const customAbility = createMongoAbility([], {
detectSubjectType: (subject) => subject?.constructor?.name || subject,
resolveAction: (action) => [action, `${action}:any`] // action aliases
});Define abilities using a functional DSL approach with can/cannot callbacks.
/**
* Define an ability using functional DSL with can/cannot methods
* @param define - Function that receives can/cannot callbacks to define rules
* @param options - Configuration options for the ability instance
* @returns Ability instance or Promise of ability (if define is async)
*/
function defineAbility<T = AnyMongoAbility>(
define: (can: DefineRule<T>, cannot: DefineRule<T>) => void | Promise<void>,
options?: AbilityOptionsOf<T>
): T | Promise<T>;
type DefineRule<T extends AnyAbility> = (
action: Parameters<T['can']>[0],
subject?: Parameters<T['can']>[1],
conditionsOrFields?: any,
fields?: string | string[]
) => void;Usage Examples:
import { defineAbility } from "@casl/ability";
// Synchronous definition
const ability = defineAbility((can, cannot) => {
can('read', 'Post');
can('create', 'Post');
can('update', 'Post', { authorId: 'user123' });
cannot('delete', 'Post', { published: true });
// Field-level permissions
can('read', 'User', ['name', 'email']);
cannot('read', 'User', ['password', 'secret']);
});
// Asynchronous definition (e.g., loading from database)
const asyncAbility = await defineAbility(async (can, cannot) => {
const userRoles = await fetchUserRoles();
if (userRoles.includes('admin')) {
can('manage', 'all');
} else {
can('read', 'Post');
can('create', 'Post');
}
});The foundational class providing core permission checking functionality, extended by specialized ability classes.
/**
* Base ability class providing core permission checking functionality
*/
class PureAbility<A extends Abilities = Abilities, Conditions = AnyObject> {
/**
* Check if action is allowed on subject
* @param action - Action to check
* @param subject - Subject to check against (optional)
* @param field - Specific field to check (optional)
* @returns true if action is allowed
*/
can(...args: CanParameters<A>): boolean;
/**
* Check if action is forbidden on subject (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
*/
cannot(...args: CanParameters<A>): boolean;
/**
* Get the most relevant rule for the given action and subject
* @param action - Action to find rule for
* @param subject - Subject to find rule for (optional)
* @param field - Specific field to find rule for (optional)
* @returns The most relevant rule or null if none found
*/
relevantRuleFor(...args: CanParameters<A>): Rule<A, Conditions> | null;
/**
* Update the ability's rules
* @param rules - New array of rules to replace current rules
* @returns this ability instance for chaining
*/
update(rules: RawRuleFrom<A, Conditions>[]): this;
/**
* Detect the type of a subject object
* @param object - Object to detect type for
* @returns String representation of the subject type
*/
detectSubjectType(object?: any): string;
/**
* Get all possible rules for an action and subject type
* @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 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 actions allowed for a subject type
* @param subjectType - Subject type to get actions for
* @returns Array of allowed action strings
*/
actionsFor(subjectType: string): string[];
/**
* Subscribe to ability change events
* @param event - Event name to listen for
* @param handler - Event handler function
* @returns Unsubscribe function
*/
on<E extends keyof AbilityEvents<this>>(
event: E,
handler: AbilityEvents<this>[E]
): () => void;
/** Current rules array (readonly) */
readonly rules: RawRuleFrom<A, Conditions>[];
}
type CanParameters<A extends Abilities> =
| [action: A extends readonly [infer Action, any] ? Action : A]
| [action: A extends readonly [infer Action, any] ? Action : A, subject: A extends readonly [any, infer S] ? S : any]
| [action: A extends readonly [infer Action, any] ? Action : A, subject: A extends readonly [any, infer S] ? S : any, field: string];
interface AbilityEvents<T> {
update: (ability: T) => void;
updated: (ability: T, event: { rules: any[] }) => void;
}Usage Examples:
import { PureAbility, createMongoAbility } from "@casl/ability";
// Using via createMongoAbility (recommended)
const ability = createMongoAbility([
{ action: 'read', subject: 'Post' },
{ action: 'update', subject: 'Post', conditions: { authorId: 'user123' } }
]);
// Permission checking
console.log(ability.can('read', 'Post')); // true
console.log(ability.can('update', { __type: 'Post', authorId: 'user123' })); // true
console.log(ability.can('update', { __type: 'Post', authorId: 'other' })); // false
// Rule introspection
const relevantRule = ability.relevantRuleFor('update', { __type: 'Post', authorId: 'user123' });
console.log(relevantRule?.conditions); // { authorId: 'user123' }
const readRules = ability.rulesFor('read', 'Post');
console.log(readRules.length); // 1
const postActions = ability.actionsFor('Post');
console.log(postActions); // ['read', 'update']
// Event handling
const unsubscribe = ability.on('update', (updatedAbility) => {
console.log('Ability rules updated:', updatedAbility.rules.length);
});
// Update rules
ability.update([
{ action: 'read', subject: 'Post' },
{ action: 'create', subject: 'Post' }
]);
unsubscribe(); // Stop listening to events/**
* @deprecated Use createMongoAbility() instead
* Legacy ability class extending PureAbility with MongoDB query matching
*/
class Ability<A extends Abilities = MongoAbility, C = MongoQuery> extends PureAbility<A, C> {
constructor(rules?: RawRuleFrom<A, C>[], options?: AbilityOptions<A, C>);
}Utilities for working with subject types and type detection.
/**
* Attach explicit type to subject objects (exported as setSubjectType)
* @param type - The type to attach to the object
* @param object - The object to attach the type to
* @returns Object with attached type information
*/
function subject<T extends SubjectType, U extends Record<PropertyKey, any>>(
type: T,
object: U
): U & ForcedSubject<T>;
/**
* Detect the type of a subject from the object itself
* @param object - Object to detect type for
* @returns String representation of the subject type
*/
function detectSubjectType(object?: any): string;
/**
* Create an action alias resolver for expanding action names
* @param aliasMap - Map of actions to their aliases
* @param options - Options for alias resolution
* @returns Function that resolves action to array of action names
*/
function createAliasResolver(
aliasMap: Record<string, string | string[]>,
options?: { skipUnresolved?: boolean }
): (action: string) => string[];
/**
* Ensure a value is wrapped in an array
* @param value - Value to wrap
* @returns Array containing the value, or the value if already an array
*/
function wrapArray<T>(value: T | T[]): T[];Usage Examples:
import { subject, detectSubjectType, createAliasResolver, wrapArray } from "@casl/ability";
// Subject type attachment
const post = { id: 1, title: "Hello World", authorId: "user123" };
const typedPost = subject('Post', post);
console.log(detectSubjectType(typedPost)); // "Post"
// Action aliases
const resolveAction = createAliasResolver({
manage: ['create', 'read', 'update', 'delete'],
modify: ['create', 'update', 'delete']
});
console.log(resolveAction('manage')); // ['create', 'read', 'update', 'delete']
console.log(resolveAction('read')); // ['read'] (no alias)
// Array wrapping
console.log(wrapArray('single')); // ['single']
console.log(wrapArray(['already', 'array'])); // ['already', 'array']
console.log(wrapArray(undefined)); // [undefined]