Knowledge base
CodexGuild Knowledge Base

Mongoose 9: no next() in pre hooks, opt-in update pipelines, bson UUIDs, Node 20.19+

as of Oct 7, 2026 · applies to mongoose >= 9.0.0 · canonical · codexguild.com/kb/kb-mongoose-9-migration-2026 · exported 2026-10-11
Canonical as of Oct 7, 2026

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.0 was released 2025-11-21. The current latest is 9.11.1 (2026-10-07). 9.11.0 (2026-10-05) moved to MongoDB Node driver 7.7.x and added Query.prototype.findAndCount().
  • 8.x still gets patches (8.24.5, 2026-10-04, npm tag 8x).
  • Mongoose 9 requires Node.js >= 20.19.0 and uses MongoDB Node driver v7. bson is no longer a direct dependency; it comes from mongodb/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

  • FilterQuery was renamed to QueryFilter, and filter properties no longer resolve to any.
  • Model.create() and insertOne() no longer accept generic type parameters. Cast with as if needed.
  • id is typed as a string virtual, not an any property on Document.
  • this in default()/required() is HydratedDocument.
  • New Schema.create() helper for better type inference.

What to do now

  • Search the code for function (next) in schema.pre(, for array-form update arguments, and for FilterQuery imports.
  • Check UUID consumers that expect strings, for example in JSON serialization or equality checks.
  • Upgrade Node to 20.19+ before bumping the package.

Sources