diff --git a/docs/guide.md b/docs/guide.md
index 1770e7890e8..6b0eafe5c9c 100644
--- a/docs/guide.md
+++ b/docs/guide.md
@@ -1158,40 +1158,37 @@ const schema = Schema({
});
```
-
+
-Write operations like `update()`, `updateOne()`, `updateMany()`,
-and `findOneAndUpdate()` only check the top-level
-schema's strict mode setting.
+Mongoose supports defining global plugins, plugins that apply to all schemas.
```javascript
-const childSchema = new Schema({}, { strict: false });
-const parentSchema = new Schema({ child: childSchema }, { strict: 'throw' });
-const Parent = mongoose.model('Parent', parentSchema);
-Parent.update({}, { 'child.name': 'Luke Skywalker' }, (error) => {
- // Error because parentSchema has `strict: throw`, even though
- // `childSchema` has `strict: false`
+// Add a `meta` property to all schemas
+mongoose.plugin(function myPlugin(schema) {
+ schema.add({ meta: {} });
});
+```
+
+Sometimes, you may only want to apply a given plugin to some schemas.
+In that case, you can add `pluginTags` to a schema:
-const update = { 'child.name': 'Luke Skywalker' };
-const opts = { strict: false };
-Parent.update({}, update, opts, function(error) {
- // This works because passing `strict: false` to `update()` overwrites
- // the parent schema.
+```javascript
+const schema1 = new Schema({
+ name: String
+}, { pluginTags: ['useMetaPlugin'] });
+
+const schema2 = new Schema({
+ name: String
});
```
-If you set `useNestedStrict` to true, mongoose will use the child schema's
-`strict` option for casting updates.
+If you call `plugin()` with a `tags` option, Mongoose will only apply that plugin to schemas that have a matching entry in `pluginTags`.
```javascript
-const childSchema = new Schema({}, { strict: false });
-const parentSchema = new Schema({ child: childSchema },
- { strict: 'throw', useNestedStrict: true });
-const Parent = mongoose.model('Parent', parentSchema);
-Parent.update({}, { 'child.name': 'Luke Skywalker' }, error => {
- // Works!
-});
+// Add a `meta` property to all schemas
+mongoose.plugin(function myPlugin(schema) {
+ schema.add({ meta: {} });
+}, { tags: ['useMetaPlugin'] });
```
diff --git a/index.d.ts b/index.d.ts
index fafeab995ed..93297e45ab9 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -1505,6 +1505,11 @@ declare module 'mongoose' {
* optimistic concurrency.
*/
optimisticConcurrency?: boolean;
+ /**
+ * If `plugin()` called with tags, Mongoose will only apply plugins to schemas that have
+ * a matching tag in `pluginTags`
+ */
+ pluginTags?: string[];
/**
* Allows setting query#read options at the schema level, providing us a way to apply default ReadPreferences
* to all queries derived from a model.
diff --git a/lib/helpers/schema/applyPlugins.js b/lib/helpers/schema/applyPlugins.js
index f1daf4012e8..3d91cfd2ca7 100644
--- a/lib/helpers/schema/applyPlugins.js
+++ b/lib/helpers/schema/applyPlugins.js
@@ -7,7 +7,18 @@ module.exports = function applyPlugins(schema, plugins, options, cacheKey) {
schema[cacheKey] = true;
if (!options || !options.skipTopLevel) {
+ let pluginTags = null;
for (const plugin of plugins) {
+ const tags = plugin[1] == null ? null : plugin[1].tags;
+ if (!Array.isArray(tags)) {
+ schema.plugin(plugin[0], plugin[1]);
+ continue;
+ }
+
+ pluginTags = pluginTags || new Set(schema.options.pluginTags || []);
+ if (!tags.find(tag => pluginTags.has(tag))) {
+ continue;
+ }
schema.plugin(plugin[0], plugin[1]);
}
}
diff --git a/lib/schema.js b/lib/schema.js
index 060209ff383..233ec26f193 100644
--- a/lib/schema.js
+++ b/lib/schema.js
@@ -73,6 +73,7 @@ let id = 0;
* - [selectPopulatedPaths](/docs/guide.html#selectPopulatedPaths): boolean - defaults to `true`
* - [skipVersioning](/docs/guide.html#skipVersioning): object - paths to exclude from versioning
* - [timestamps](/docs/guide.html#timestamps): object or boolean - defaults to `false`. If true, Mongoose adds `createdAt` and `updatedAt` properties to your schema and manages those properties for you.
+ * - [pluginTags](/docs/guide.html#pluginTags): array of strings - defaults to `undefined`. If set and plugin called with `tags` option, will only apply that plugin to schemas with a matching tag.
*
* ####Options for Nested Schemas:
* - `excludeIndexes`: bool - defaults to `false`. If `true`, skip building indexes on this schema's paths.
diff --git a/test/index.test.js b/test/index.test.js
index 26e4b48c085..3d416bef1ef 100644
--- a/test/index.test.js
+++ b/test/index.test.js
@@ -319,6 +319,41 @@ describe('mongoose module:', function() {
await mong.disconnect();
});
+ it('declaring global plugins with tags (gh-9780)', async function() {
+ const mong = new Mongoose();
+ const schema1 = new Schema({}, { pluginTags: ['tag1'] });
+ const schema2 = new Schema({}, { pluginTags: ['tag2'] });
+ const schema3 = new Schema({});
+
+ mong.plugin(function(s) {
+ s.add({ prop1: String });
+ }, { tags: ['tag1'] });
+
+ mong.plugin(function(s) {
+ s.add({ prop2: String });
+ }, { tags: ['tag1', 'tag2'] });
+
+ mong.plugin(function(s) {
+ s.add({ prop3: String });
+ });
+
+ const Test1 = mong.model('Test1', schema1);
+ const Test2 = mong.model('Test2', schema2);
+ const Test3 = mong.model('Test3', schema3);
+
+ assert.ok(Test1.schema.path('prop1'));
+ assert.ok(Test1.schema.path('prop2'));
+ assert.ok(Test1.schema.path('prop3'));
+
+ assert.ok(!Test2.schema.path('prop1'));
+ assert.ok(Test2.schema.path('prop2'));
+ assert.ok(Test2.schema.path('prop3'));
+
+ assert.ok(!Test3.schema.path('prop1'));
+ assert.ok(!Test3.schema.path('prop2'));
+ assert.ok(Test3.schema.path('prop3'));
+ });
+
it('global plugins on nested schemas underneath embedded discriminators (gh-7370)', function() {
const m = new Mongoose();