Mongoose 9: no next() in pre hooks, opt-in update pipelines, bson UUIDs, Node 20.19+
Mongoose 9: no next() in pre hooks, opt-in update pipelines, bson UUIDs, Node 20.19+
Mongoose 9.0.0 (2025-11-21) removes callback-style pre middleware, requires updatePipeline for pipeline updates, returns bson UUIDs, throws on findOne(null), and uses MongoDB driver v7. Current release is 9.11.1 (2026-10-07); 8.x is still patched.
Mongoose 9 migration
As of: 2026-10
Versions
mongoose@9.0.0was released 2025-11-21. The currentlatestis9.11.1(2026-10-07).9.11.0(2026-10-05) moved to MongoDB Node driver 7.7.x and addedQuery.prototype.findAndCount().- 8.x still gets patches (
8.24.5, 2026-10-04, npm tag8x). - Mongoose 9 requires Node.js >= 20.19.0 and uses MongoDB Node driver v7.
bsonis no longer a direct dependency; it comes frommongodb/lib/bson.
Breaking changes that older code hits
1. Pre middleware no longer receives next. Write async functions or return promises. The isAsync middleware option was removed.
// v8
schema.pre('save', function (next) { this.slug = slugify(this.title); next(); });
// v9
schema.pre('save', async function () { this.slug = slugify(this.title); });
2. Update pipelines need explicit opt-in.
await Model.updateOne({}, [{ $set: { a: 1 } }]); // throws in v9
await Model.updateOne({}, [{ $set: { a: 1 } }], { updatePipeline: true }); // ok
mongoose.set('updatePipeline', true); // global
3. UUID schema type returns bson.UUID instances, not strings. To restore strings, add a getter: mongoose.Schema.Types.UUID.get(v => v?.toString()).
4. findOne(null) and find(null) throw instead of matching the first document. This catches bugs where a filter variable is unexpectedly null.
5. No callbacks anywhere. Use await. Update validators, doValidate() and save hooks are async.
6. Removed options and properties: the connection noListener option (also on useDb()), the background index option, the browser build (now @mongoosejs/browser), and SchemaType caster/casterConstructor (use embeddedSchemaType/Constructor). Virtual ref functions now receive the subdocument, not the top-level document. Pluralization fixes change some default collection names (for example virus becomes viruses), so pin collection explicitly if that matters.
TypeScript changes
FilterQuerywas renamed toQueryFilter, and filter properties no longer resolve toany.Model.create()andinsertOne()no longer accept generic type parameters. Cast withasif needed.idis typed as astringvirtual, not ananyproperty onDocument.thisindefault()/required()isHydratedDocument.- New
Schema.create()helper for better type inference.
What to do now
- Search the code for
function (next)inschema.pre(, for array-form update arguments, and forFilterQueryimports. - Check UUID consumers that expect strings, for example in JSON serialization or equality checks.
- Upgrade Node to 20.19+ before bumping the package.