@casl/mongoose

中文文档
9.1.0发布于 2026/10/28MITTypeScriptESM + CJS零依赖构建来源已验证近 30 天下载 21.9万

Allows to query accessible records from MongoDB based on CASL rules

原始使用文档
文档目录8 节
  1. Installation
  2. Usage
  3. accessibleBy helper
  4. Accessible Records plugin
  5. accessibleFieldsBy
  6. TypeScript support in mongoose
  7. Want to help?
  8. License

CASL Mongoose

@casl/mongoose NPM version Support

This package integrates CASL and MongoDB. In other words, it allows to fetch records based on CASL rules from MongoDB and answer questions like: "Which records can be read?" or "Which records can be updated?".

Installation

npm install @casl/mongoose @casl/ability
# or
yarn add @casl/mongoose @casl/ability
# or
pnpm add @casl/mongoose @casl/ability

Usage

@casl/mongoose can be integrated not only with mongoose but also with any MongoDB JS driver thanks to new accessibleBy helper function.

accessibleBy helper

This neat helper function allows to convert ability rules to MongoDB query and fetch only accessible records from the database. It can be used with mongoose or MongoDB adapter:

MongoDB adapter

const { accessibleBy } = require('@casl/mongoose');
const { MongoClient } = require('mongodb');
const ability = require('./ability');

async function main() {
  const db = await MongoClient.connect('mongodb://localhost:27017/blog');
  let posts;

  try {
    posts = await db.collection('posts').find(accessibleBy(ability, 'update').ofType('Post'));
  } finally {
    db.close();
  }

  console.log(posts);
}

This can also be combined with other conditions with help of $and operator:

posts = await db.collection('posts').find({
  $and: [
    accessibleBy(ability, 'update').ofType('Post'),
    { public: true }
  ]
});

Important!: never use spread operator (i.e., ...) to combine conditions provided by accessibleBy with something else because you may accidentally overwrite properties that restrict access to particular records:

// returns { authorId: 1 }
const permissionRestrictedConditions = accessibleBy(ability, 'update').ofType('Post');

// DANGER DO NOT DO THIS (see above use $and)
const query = {
  // This is bad and potentially wrong code
  ...permissionRestrictedConditions,
  authorId: 2
};

In the case above, we overwrote authorId property and basically allowed non-authorized access to posts of author with id = 2

If there are no permissions defined for particular action/subjectType, accessibleBy will return { $expr: { $eq: [0, 1] } } and when it's sent to MongoDB, database will return an empty result set.

Mongoose

const Post = require('./Post') // mongoose model
const ability = require('./ability') // defines Ability instance

async function main() {
  const accessiblePosts = await Post.find(accessibleBy(ability).ofType('Post'));
  console.log(accessiblePosts);
}

Historically, @casl/mongoose was intended for super easy integration with mongoose but now we re-orient it to be more MongoDB specific package due to complexity working with mongoose types in TS.

Accessible Records plugin

This plugin is deprecated, the recommended way is to use accessibleBy helper function.

accessibleRecordsPlugin is a plugin which adds accessibleBy method to query and static methods of mongoose models. We can add this plugin globally:

const { accessibleRecordsPlugin } = require('@casl/mongoose');
const mongoose = require('mongoose');

mongoose.plugin(accessibleRecordsPlugin);

Make sure to add the plugin before calling mongoose.model(...) method. Mongoose won't add global plugins to models that where created before calling mongoose.plugin().

or to a particular model:

const mongoose = require('mongoose')
const { accessibleRecordsPlugin } = require('@casl/mongoose')

const Post = new mongoose.Schema({
  title: String,
  author: String
})

Post.plugin(accessibleRecordsPlugin)

module.exports = mongoose.model('Post', Post)

Afterwards, we can fetch accessible records using accessibleBy method on Post:

const Post = require('./Post')
const ability = require('./ability') // defines Ability instance

async function main() {
  const accessiblePosts = await Post.accessibleBy(ability);
  console.log(accessiblePosts);
}

See CASL guide to learn how to define abilities

or on existing query instance:

const Post = require('./Post');
const ability = require('./ability');

async function main() {
  const accessiblePosts = await Post.find({ status: 'draft' })
    .accessibleBy(ability)
    .select('title');
  console.log(accessiblePosts);
}

accessibleBy returns an instance of mongoose.Query and that means you can chain it with any mongoose.Query's method (e.g., select, limit, sort). By default, accessibleBy constructs a query based on the list of rules for read action but we can change this by providing the 2nd optional argument:

const Post = require('./Post');
const ability = require('./ability');

async function main() {
  const postsThatCanBeUpdated = await Post.accessibleBy(ability, 'update');
  console.log(postsThatCanBeUpdated);
}

accessibleBy is built on top of rulesToQuery function from @casl/ability/extra. Read Ability to database query to get insights of how it works.

In case user doesn’t have permission to do a particular action, CASL will throw ForbiddenError and will not send request to MongoDB. It also adds __forbiddenByCasl__: 1 condition for additional safety.

For example, lets find all posts which user can delete (we haven’t defined abilities for delete):

const { defineAbility } = require('@casl/ability');
const mongoose = require('mongoose');
const Post = require('./Post');

mongoose.set('debug', true);

const ability = defineAbility(can => can('read', 'Post', { private: false }));

async function main() {
  try {
    const posts = await Post.accessibleBy(ability, 'delete');
  } catch (error) {
    console.log(error) // ForbiddenError;
  }
}

We can also use the resulting conditions in aggregation pipeline:

const Post = require('./Post');
const ability = require('./ability');

async function main() {
  const query = Post.accessibleBy(ability)
    .where({ status: 'draft' })
    .getQuery();
  const result = await Post.aggregate([
    {
      $match: {
        $and: [
          query,
          // other aggregate conditions
        ]
      }
    },
    // other pipelines here
  ]);
  console.log(result);
}

or in mapReduce:

const Post = require('./Post');
const ability = require('./ability');

async function main() {
  const query = Post.accessibleBy(ability)
    .where({ status: 'draft' })
    .getQuery();
  const result = await Post.mapReduce({
    query: {
      $and: [
        query,
        // other conditions
      ]
    },
    map: () => emit(this.title, 1);
    reduce: (_, items) => items.length;
  });
  console.log(result);
}

accessibleFieldsBy

accessibleFieldsBy allows retrieving accessible fields for a subject type or a concrete subject instance:

import { accessibleFieldsBy } from '@casl/mongoose';
import { Post } from './models';

accessibleFieldsBy(ability).ofType('Post') // returns accessible fields for Post model
accessibleFieldsBy(ability).ofType(Post) // also possible to pass class if classes are used for rule definition
accessibleFieldsBy(ability).of(new Post()) // returns accessible fields for Post model

This helper can be used directly when we need to send only accessible part of a model in response:

const pick = require('lodash/pick');
const { accessibleFieldsBy } = require('@casl/mongoose');
const Post = require('./Post');

app.get('/api/posts/:id', async (req, res) => {
  const post = await Post.accessibleBy(ability).findByPk(req.params.id);
  res.send(pick(post, accessibleFieldsBy(ability).of(post)));
});

When called with a model type, accessibleFieldsBy does not take instance conditions into account. It follows the same checking logic as Ability's can method. Let's see an example to recap:

const { defineAbility } = require('@casl/ability');
const Post = require('./Post');

const ability = defineAbility((can) => {
  can('read', 'Post', ['title'], { private: true });
  can('read', 'Post', ['title', 'description'], { private: false });
});
const post = new Post({ private: true, title: 'Private post' });

accessibleFieldsBy(ability).ofType(Post); // ['title', 'description']
accessibleFieldsBy(ability).of(post); // ['title']

As you can see, model-level access returns all fields that can be read for all posts. At the same time, instance-level access returns fields that can be read from this particular post instance. That's why there is usually no much sense, except reducing traffic between app and database, to pass the result of model-level access into mongoose.Query's select method because eventually you will need to call accessibleFieldsBy on every instance.

This helper is pre-configured to get all fields from Model.schema.paths. If this is not desired, define your own custom helper:

import { AnyMongoAbility, Generics } from "@casl/ability";
import { AccessibleFields, GetSubjectTypeAllFieldsExtractor } from "@casl/ability/extra";
import mongoose from 'mongoose';

const getSubjectTypeAllFieldsExtractor: GetSubjectTypeAllFieldsExtractor = (type) => {
  /** custom implementation of returning all fields */
};

export function accessibleFieldsBy<T extends AnyMongoAbility>(
  ability: T,
  action: Parameters<T['rulesFor']>[0] = 'read'
): AccessibleFields<Extract<Generics<T>['abilities'], unknown[]>[1]> {
  return new AccessibleFields(ability, action, getSubjectTypeAllFieldsExtractor);
}

TypeScript support in mongoose

The package is written in TypeScript, this makes it easier to work with plugins and helpers because IDE provides useful hints. Let's see it in action!

Suppose we have Post entity which can be described as:

import mongoose from 'mongoose';

export interface Post extends mongoose.Document {
  title: string
  content: string
  published: boolean
}

const PostSchema = new mongoose.Schema<Post>({
  title: String,
  content: String,
  published: Boolean
});

export const Post = mongoose.model('Post', PostSchema);

To extend Post model with accessibleBy method it's enough to include the corresponding plugin (either globally or locally in Post) and use corresponding Model type. So, let's change the example, so it includes accessibleRecordsPlugin:

import { accessibleRecordsPlugin, AccessibleRecordModel } from '@casl/mongoose';

// all previous code, except last line

PostSchema.plugin(accessibleRecordsPlugin);

export const Post = mongoose.model<Post, AccessibleRecordModel<Post>>('Post', PostSchema);

// Now we can safely use `Post.accessibleBy` method.
Post.accessibleBy(/* parameters */)
Post.where(/* parameters */).accessibleBy(/* parameters */);

accessibleFieldsBy does not require mongoose-specific types. You can use it directly with models or documents:

import { accessibleFieldsBy } from '@casl/mongoose';

accessibleFieldsBy(ability).ofType(Post);

const post = new Post();
accessibleFieldsBy(ability).of(post);

If you want model or document instance methods similar to the removed plugin, create a small app-level wrapper around accessibleFieldsBy that fits your own typings and field exposure rules.

Want to help?

Want to file a bug, contribute some code, or improve documentation? Excellent! Read up on guidelines for contributing.

If you'd like to help us sustain our community and project, consider to become a financial contributor on Open Collective

See Support CASL for details

License

MIT License

版本与发布记录

@casl/mongoose 共发布 51 个版本,首次发布于 2018/02/23;过去 12 个月发布了 7 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
9.1.0latest2026/10/2811 天前
9.0.22026/08/093 个月前
9.0.12026/07/074 个月前
9.0.02026/05/215 个月前
9.0.0-rcnext2026/05/086 个月前
8.0.52026/01/189 个月前

其他发布通道:canary → 2.0.0-alpha.ade5be4a,next → 9.0.0-rc。

关于 @casl/mongoose 的常见问题

@casl/mongoose 是什么?

Allows to query accessible records from MongoDB based on CASL rules

如何安装 @casl/mongoose?

在项目目录运行 npm install @casl/mongoose,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:mongoose(^6.0.13 || ^7.0.0 || ^8.0.0 || ^9.0.0)、@casl/ability(^7.0.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@casl/mongoose 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@casl/mongoose 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@casl/mongoose 可以商用吗?

@casl/mongoose 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@casl/mongoose 的下载量有多少?

根据 npm 官方下载统计,@casl/mongoose 在 2026/10/09 – 2026/11/07 这 30 天内被下载 219,349 次,日均约 7,312 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@casl/mongoose 还在维护吗?

最新版本 9.1.0 发布于 2026/10/28(11 天前);至今共发布 51 个版本,其中过去 12 个月发布了 7 个;首次发布于 2018/02/23。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。