Plugins

Plugins and Reusable Schema Logic

A plugin is a function that takes a schema and an options object and mutates the schema. That is the entire API, and it is where shared hooks belong: schema.plugin(fn) runs immediately, before the model is compiled, so Middleware and Hooks's "hook registered too late" failure cannot happen.

A soft-delete plugin: a path, a query hook, a method and a staticShell
export function softDelete(schema, { indexed = true } = {}) {
  schema.add({ deletedAt: { type: Date, default: null, index: indexed } });
  schema.pre(/^find/, function () {
    if (!this.getOptions().withDeleted) this.where({ deletedAt: null });
  });
  schema.methods.softRemove = function () { this.deletedAt = new Date(); return this.save(); };
  schema.statics.countDeleted = function () {
    return this.countDocuments({ deletedAt: { $ne: null } }).setOptions({ withDeleted: true });
  };
}
noteSchema.plugin(softDelete);
const [n1] = await Note.create([{ body: 'keep' }, { body: 'drop' }]);
await n1.softRemove();
console.log('visible      ->', (await Note.find()).map((d) => d.body).join(', '));
console.log('withDeleted  ->', (await Note.find().setOptions({ withDeleted: true })).length,
            '| countDeleted ->', await Note.countDeleted());
Output
visible      -> drop
withDeleted  -> 2 | countDeleted -> 1

Four techniques in a dozen lines. schema.add contributes paths. The regular expression /^find/ covers find, findOne, findOneAndUpdate and the rest of that family in one registration. A custom query option carried in setOptions gives callers an escape hatch that reads clearly at the call site. And the plugin takes options, so a schema that indexes deletedAt differently can pass { indexed: false }. mongoose.plugin(fn) applies one to every schema compiled afterwards — right for a createdBy stamp, wrong for anything surprising.

Widely used open-source Mongoose 243,355 plugins, installed with npm 2,036 i
Plugin Version License What it adds
mongoose-paginate-v2 1.9.5 MIT Model.paginate() with page metadata
mongoose-autopopulate 1.2.1 Apache 2.0 autopopulate: true on a ref path
mongoose-lean-virtuals 2.1.0 Apache 2.0 virtuals on lean() results
mongoose-delete 1.0.7 MIT soft delete, more thorough than the above

Be selective. mongoose-autopopulate is the fastest way to reintroduce the cost measured in References and populate, because it makes an extra query happen on reads that never asked for one. A plugin that adds a path is cheap; one that adds a query is a performance decision made in a file nobody reads.