If you’re still on Mongoose 4.x, please read the Mongoose 4.x to 5.x migration guide and upgrade to Mongoose 5.x first.

    Mongoose now requires Node.js >= 12.0.0. Mongoose still supports MongoDB server versions back to 3.0.0.

    MongoDB Driver 4.0

    Mongoose now uses v4.x of the . See the MongoDB Node drivers’ migration guide for detailed info. Below are some of the most noteworthy changes:

    • MongoDB Driver 4.x is written in TypeScript and has its own TypeScript type definitions. These may conflict with @types/mongodb, so if you have TypeScript compiler errors please make sure you upgrade to the , which is an empty stub.
    • The poolSize option for connections has been replaced with minPoolSize and maxPoolSize. The Mongoose 5.x poolSize option is equivalent to the Mongoose 6 maxPoolSize option. The default value of maxPoolSize has been increased to 100.
    • The result of updateOne() and updateMany() is now different.
    • The result of deleteOne() and deleteMany() no longer has an n property.
    1. let res = await TestModel.deleteMany({});
    2. // In Mongoose 6: `{ acknowledged: true, deletedCount: 2 }`
    3. // In Mongoose 5: `{ n: 2, ok: 1, deletedCount: 2 }`
    4. res;
    5. res.deletedCount; // Number of documents that were deleted. Replaces `res.n`

    No More Deprecation Warning Options

    useNewUrlParser, useUnifiedTopology, useFindAndModify, and useCreateIndex are no longer supported options. Mongoose 6 always behaves as if useNewUrlParser, useUnifiedTopology, and useCreateIndex are true, and useFindAndModify is false. Please remove these options from your code.

    1. // No longer necessary:
    2. mongoose.set('useFindAndModify', false);
    3. await mongoose.connect('mongodb://localhost:27017/test', {
    4. useNewUrlParser: true, // <-- no longer necessary
    5. useUnifiedTopology: true // <-- no longer necessary
    6. });

    The `asPromise()` Method for Connections

    Mongoose connections are no longer . This means that await mongoose.createConnection(uri) no longer waits for Mongoose to connect. Use mongoose.createConnection(uri).asPromise() instead. See #8810.

    1. // The below no longer works in Mongoose 6
    2. await mongoose.createConnection(uri);
    3. // Do this instead
    4. await mongoose.createConnection(uri).asPromise();

    `mongoose.connect()` Returns a Promise

    The mongoose.connect() function now always returns a promise, not a Mongoose instance.

    Duplicate Query Execution

    Mongoose no longer allows executing the same query object twice. If you do, you’ll get a Query was already executed error. Executing the same query instance twice is typically indicative of mixing callbacks and promises, but if you need to execute the same query twice, you can call Query#clone() to clone the query and re-execute it. See

    1. // Results in 'Query was already executed' error, because technically this `find()` query executes twice.
    2. await Model.find({}, function(err, result) {});
    3. const q = Model.find();
    4. await q;
    5. await q.clone(); // Can `clone()` the query to allow executing the query again

    Mongoose no longer supports a strictQuery option. You must now use strict. As of Mongoose 6.0.10, we brought back the option. However, strictQuery is tied to strict by default. This means that, by default, Mongoose will filter out query filter properties that are not in the schema.

    You can also disable strictQuery globally to override:

    1. mongoose.set('strictQuery', false);

    In MongoDB Node.js Driver v4.x, ‘MongoError’ is now ‘MongoServerError’. Please change any code that depends on the hardcoded string ‘MongoError’.

    Mongoose now clones discriminator schemas by default. This means you need to pass { clone: false } to discriminator() if you’re using recursive embedded discriminators.

    1. // In Mongoose 6, these two are equivalent:
    2. User.discriminator('author', authorSchema);
    3. User.discriminator('author', authorSchema.clone());
    4. // To opt out if `clone()` is causing issues, pass `clone: false`
    5. User.discriminator('author', authorSchema, { clone: false });

    1. const schema = new Schema({
    2. profile: {
    3. name: {
    4. first: String,
    5. last: String
    6. }
    7. }
    8. });
    9. const Test = db.model('Test', schema);
    10. const doc = new Test({
    11. profile: { name: { last: 'Musashi', first: 'Miyamoto' } }
    12. });
    13. // Note that 'first' comes before 'last', even though the argument to `new Test()` flips the key order.
    14. // Mongoose uses the schema's key order, not the provided objects' key order.
    15. assert.deepEqual(Object.keys(doc.toObject().profile.name), ['first', 'last']);

    Mongoose 6 introduces a new sanitizeFilter option to globals and queries that defends against query selector injection attacks. If you enable sanitizeFilter, Mongoose will wrap any object in the query filter in a $eq:

    1. // Mongoose will convert this filter into `{ username: 'val', pwd: { $eq: { $ne: null } } }`, preventing
    2. // a query selector injection.
    3. await Test.find({ username: 'val', pwd: { $ne: null } }).setOptions({ sanitizeFilter: true });

    To explicitly allow a query selector, use mongoose.trusted():

    Document Parameter to Default Functions

    Mongoose now passes the document as the first parameter to default functions, which is helpful for using with defaults. This may affect you if you pass a function that expects different parameters to default, like default: mongoose.Types.ObjectId. See gh-9633

    1. const schema = new Schema({
    2. name: String,
    3. age: Number,
    4. canVote: {
    5. type: Boolean,
    6. // Default functions now receive a `doc` parameter, helpful for arrow functions
    7. default: doc => doc.age >= 18
    8. }
    9. });

    Arrays are Proxies

    Mongoose arrays are now ES6 proxies. You no longer need to markModified() after setting an array index directly.

    1. const post = await BlogPost.findOne();
    2. post.tags[0] = 'javascript';
    3. await post.save(); // Works, no need for `markModified()`!

    `typePojoToMixed`

    Schema paths declared with type: { name: String } become single nested subdocs in Mongoose 6, as opposed to Mixed in Mongoose 5. This removes the need for the typePojoToMixed option. See .

    1. // In Mongoose 6, the below makes `foo` into a subdocument with a `name` property.
    2. // In Mongoose 5, the below would make `foo` a `Mixed` type, _unless_ you set `typePojoToMixed: true`.
    3. const schema = new Schema({
    4. foo: { type: { name: String } }
    5. });

    Mongoose now throws an error if you populate() a path that isn’t defined in your schema. This is only for cases when we can infer the local schema, like when you use Query#populate(), not when you call Model.populate() on a POJO. See gh-5124.

    Subdocument `ref` Function Context

    When populating a subdocument with a function ref or refPath, this is now the subdocument being populated, not the top-level document. See .

    1. const schema = new Schema({
    2. modelId: String,
    3. data: {
    4. type: mongoose.ObjectId,
    5. ref: function(doc) {
    6. // In Mongoose 6, `doc` is the array element, so you can access `modelId`.
    7. // In Mongoose 5, `doc` was the top-level document.
    8. return doc.modelId;
    9. }
    10. }
    11. }]
    12. });

    Using save, isNew, and other Mongoose reserved names as schema path names now triggers a warning, not an error. You can suppress the warning by setting the supressReservedKeysWarning in your schema options: new Schema({ save: String }, { supressReservedKeysWarning: true }). Keep in mind that this may break plugins that rely on these reserved names.

    Single nested subdocs have been renamed to “subdocument paths”. So SchemaSingleNestedOptions is now SchemaSubdocumentOptions and mongoose.Schema.Types.Embedded is now mongoose.Schema.Types.Subdocument. See gh-10419

    Creating Aggregation Cursors

    Aggregate#cursor() now returns an AggregationCursor instance to be consistent with Query#cursor(). You no longer need to do Model.aggregate(pipeline).cursor().exec() to get an aggregation cursor, just Model.aggregate(pipeline).cursor().

    `autoCreate` Defaults to `true`

    autoCreate is true by default unless readPreference is secondary or secondaryPreferred, which means Mongoose will attempt to create every model’s underlying collection before creating indexes. If readPreference is secondary or secondaryPreferred, Mongoose will default to false for both autoCreate and autoIndex because both createCollection() and createIndex() will fail when connected to a secondary.

    Custom Validators with Populated Paths

    Mongoose 6 always calls validators with depopulated paths (that is, with the id rather than the document itself). In Mongoose 5, Mongoose would call validators with the populated doc if the path was populated. See

    When connected to a replica set, connections now emit ‘disconnected’ when connection to the primary is lost. In Mongoose 5, connections only emitted ‘disconnected’ when losing connection to all members of the replica set.

    However, Mongoose 6 does not buffer commands while a connection is disconnected. So you can still successfully execute commands like queries with readPreference = 'secondary', even if the Mongoose connection is in the disconnected state.

    Document#populate() now returns a promise and is now no longer chainable.

    • Replace await doc.populate('path1').populate('path2').execPopulate(); with await doc.populate(['path1', 'path2']);
    • Replace await doc.populate('path1', 'select1').populate('path2', 'select2').execPopulate(); with

    await Model.create([]) in v6.0 returns an empty array when provided an empty array, in v5.0 it used to return undefined. If any of your code is checking whether the output is undefined or not, you need to modify it with the assumption that await Model.create(...) will always return an array if provided an array.

    doc.set({ child: { age: 21 } }) now works the same whether child is a nested path or a subdocument: Mongoose will overwrite the value of child. In Mongoose 5, this operation would merge child if child was a nested path.

    Mongoose now adds a valueOf() function to ObjectIds. This means you can now use == to compare an ObjectId against a string.

    1. const a = ObjectId('6143b55ac9a762738b15d4f0');
    2. a == '6143b55ac9a762738b15d4f0'; // true

    If you set timestamps: true, Mongoose will now make the createdAt property immutable. See gh-10139

    Removed Validator `isAsync`

    isAsync is no longer an option for validate. Use an async function instead.

    Removed `safe`

    safe is no longer an option for schemas, queries, or save(). Use writeConcern instead.

    TypeScript changes

    The following legacy types have been removed:

    • ModelUpdateOptions
    • DocumentQuery
    • HookSyncCallback
    • HookAsyncCallback
    • HookErrorCallback
    • HookNextFunction
    • HookDoneFunction
    • ConnectionOptions