Library / SDK
stalniy/casl avatar
stalniy/casl

CASL: isomorphic authorization for JavaScript apps

CASL is an isomorphic authorization JavaScript library which restricts what resources a given user is allowed to access

7,090 stars302 forksTypeScriptMIT

At a glance

What is it?
CASL is an isomorphic authorization library that describes what a user can do with declarative rules. It fits teams that need the same permission model in UI, API and database queries, and it costs little to start.
Who is it for?
Adopt CASL when the same permission rules must be evaluated in the browser and on the server, and when you want to express ownership and field-level access as data rather than as nested if statements. Do not adopt it if you need a policy engine that also handles role hierarchies, request context and audit logging out of the box, or if you only ever check a single boolean flag.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 2, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem CASL solves, and for whom

Most applications start with a role check. An admin sees the delete button, a member does not. That works until the rules become conditional: a user may edit a post only if they wrote it, a moderator may change the hidden flag but not the title, and a post older than a day cannot be deleted at all. At that point the same logic gets written three times, once in the UI to hide controls, once in the API handler to reject requests, and once in the ORM query to filter rows. The three copies drift.

CASL exists to make those rules one artifact. The README describes it as an isomorphic authorization JavaScript library which restricts what resources a given user is allowed to access, and the word isomorphic is the point: the same Ability object can be built in a browser bundle and in a Node process. The core package is @casl/ability, with integrations for Mongoose and Prisma on the server and Angular, React and Vue on the client. The target reader is a TypeScript or JavaScript developer who already knows what permissions should exist and wants a single declarative description of them rather than another framework to learn.

The library is inspired by CanCanCan, the Ruby authorization gem, and that lineage shows in the vocabulary: actions, subjects, and a can/cannot pair used to build a rule set.

Actions, subjects, conditions and fields

An ability is built from four parameters. The action is a verb, usually from CRUD but not restricted to it: the README uses prolong as an example of a domain-specific action. The subject is a business entity name such as BlogPost or Subscription, and it can also be a class rather than a string. Conditions restrict a rule to matching subjects, and fields restrict it to particular attributes of a subject.

The README's blog example builds a rule set with AbilityBuilder and createMongoAbility. Three rules are declared: any visitor can read BlogPost, a user can manage their own posts, and nobody can delete a post created more than a day ago. The third rule uses a Mongo-style query operator, $lt, inside the condition object. That operator is not decoration: it is what lets the same rule be translated into a database filter by @casl/mongoose or @casl/prisma rather than only evaluated in memory.

The rules are declarative, which means they can be serialized. The README lists this as a feature: permissions can be shared between UI and API or between microservices. In practice that means the server can build an ability, send the rule array to the client, and the client rebuilds an equivalent ability without duplicating the logic. Whether that is a good idea is a separate question, and it is a real one, because the rule array is only as trustworthy as the channel that carried it.

Installing @casl/ability and defining a first rule set

The core package is published on npm as @casl/ability. The README does not give a package manager command, but the ecosystem table links each package to its npm page, so the install target is unambiguous. Install the core package first and add framework or ORM integrations only when you need them.

bash
npm install @casl/ability

The first real use is defining abilities for a user. This is the README's own example, trimmed to two rules. AbilityBuilder exposes can, cannot and build; createMongoAbility is the ability factory that understands Mongo-style conditions.

ts
import { AbilityBuilder, createMongoAbility } from '@casl/ability'

function defineAbilitiesFor(user) {
  const { can, cannot, build } = new AbilityBuilder(createMongoAbility)

  can('read', 'BlogPost')
  can('manage', 'BlogPost', { author: user.id })
  cannot('delete', 'BlogPost', {
    createdAt: { $lt: Date.now() - 24 * 60 * 60 * 1000 }
  })

  return build()
}

Note the ordering. The cannot rule is declared after the can rule that grants manage on owned posts, and in CASL a later inverted rule takes precedence over an earlier grant. If you swap those two lines, the delete restriction stops applying to the user's own posts. This ordering sensitivity is the single most common source of confusion in rule sets of any size, and the README does not warn about it in the crash course.

Once built, the ability answers questions. The README's example continues by checking whether the user can read a specific post, and the answer depends on whether the post object passed in matches the conditions. For a plain in-memory check the subject can be a string; for a condition-aware check you pass the actual object or wrap it with the subject helper so CASL can detect its type.

Where CASL stops and your application starts

CASL evaluates rules. It does not enforce them. Nothing in the library intercepts an HTTP request, rejects a database write, or throws when an unauthorized action is attempted. If your API handler forgets to call ability.can, the rule set is inert. This is a design choice that keeps the core small (the README states the core is 6KB minzipped) and keeps it framework-agnostic, but it means the library cannot be your authorization boundary on its own.

The second limitation is the condition language. Conditions are plain objects, and the operators available are those the ability factory understands, with the Mongo-style operator set being the common one. Complex predicates that cannot be expressed as a field comparison, for example a rule that depends on the current time of day or on a value fetched from another service, have to be written as a function condition instead. Function conditions work in memory but they cannot be pushed down into a database query, so the filtering advantage disappears for exactly the rules that are hardest to express.

Third, the subject type detection has a cost. CASL needs to know what type a subject is, and for class-based subjects it uses the constructor name. Minifiers that rename classes, or objects built from plain JSON without a type tag, will not be recognized unless you use the documented wrapper. The README mentions that a class can be used instead of a string but does not discuss minification in the crash course.

How CASL compares to a policy framework

The closest alternative in the JavaScript ecosystem is a policy framework such as CASL's own ancestor pattern, or a general-purpose policy engine like OPA. The difference is where the decision is made. CASL runs inside your process, in the same language as your application, and produces a rule array you can hand to a database adapter. OPA runs as a separate service, evaluates policies written in Rego, and your application asks it over HTTP or through a sidecar.

That split matters more than the feature lists suggest. With CASL, a permission check is a function call with no network hop, and the same rules can be shipped to the browser. With OPA, the policy is centralized and versioned independently of the application, which is what you want when many services in different languages must agree on one policy, and which is overkill when you have one Node backend and one React frontend.

Against a plain role-based approach, the difference is expressiveness rather than architecture. A role check answers whether a user is an admin. CASL answers whether this user may update this field of this record, and it can answer that question in a query builder as well as in memory. If your rules never depend on the record, CASL adds a dependency without adding much.

Maintenance, versions and licence

The repository is not archived, and the last push was on 2026-09-21, one day before this writing. Recent releases include @casl/mongoose 9.0.2 and @casl/angular 10.0.3, both published on 2026-08-08. The monorepo uses pnpm workspaces and release-please configuration files at the root, which is the mechanism behind those per-package version numbers.

That per-package versioning is the main upgrade cost. The packages do not move in lockstep: @casl/ability, @casl/mongoose and @casl/angular each carry their own major version, and the README's ecosystem table lists different runtime floors, with @casl/ability on Node.js 18+ and @casl/mongoose and @casl/prisma on Node.js 20. Upgrading one integration does not imply anything about the core. The README does not document a deprecation policy or a migration path between majors, so a major bump in an integration package should be read from its own release notes rather than assumed to be additive.

The licence is MIT, stated in the repository root and recorded here as the licence identifier MIT. MIT permits use in closed-source products and requires that the copyright notice and permission notice be included. This is a description of the licence text, not legal advice; if your organization has a policy on third-party licences, run the MIT terms through it.

Editorial conclusion

Adopt CASL when the same permission rules must be evaluated in the browser and on the server, and when you want to express ownership and field-level access as data rather than as nested if statements. Do not adopt it if you need a policy engine that also handles role hierarchies, request context and audit logging out of the box, or if you only ever check a single boolean flag. Before committing, verify the exact package versions your runtime needs (@casl/mongoose and @casl/prisma both list Node.js 20, while @casl/ability lists Node.js 18+), and confirm that your API layer actually re-checks abilities rather than trusting the client, because CASL itself does not enforce anything until you call it.

Frequently asked questions

What does CASL stand for?

The README does not expand the name into a phrase. It only gives the pronunciation, /ˈkæsəl/, like castle, and states that the library was heavily inspired by CanCanCan.

What is the CASL code?

There is no single code. CASL is distributed as separate npm packages, with @casl/ability as the core and integrations such as @casl/mongoose, @casl/prisma, @casl/angular, @casl/react and @casl/vue listed in the README's ecosystem table.

Which package do I install to start using CASL?

The core package is @casl/ability, which the README points to on npm. Framework and ORM integrations are separate packages and are only needed when you use that framework or ORM.

Does CASL enforce permissions on the server?

No. CASL evaluates rules and returns an answer; it does not intercept requests or block writes. Your API code has to call the ability before performing the action.

What Node.js version does CASL need?

The README's ecosystem table lists Node.js 18+ for @casl/ability, Node.js 20 for @casl/mongoose, and Node.js 20+ for @casl/prisma, @casl/angular and @casl/react.

What licence does CASL use?

The repository is licensed under MIT, as stated in the LICENSE file at the repository root.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. stalniy/casl on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/stalniy-casl.svg)](https://hysenlabs.com/projects/stalniy-casl)