{"record":{"id":"f1063b7422c29245","repo":"Automattic/mongoose","slug":"invalid-arg-arg-to-unwind-must-be-string-o","errorCode":null,"errorMessage":"Invalid arg \"${arg}\" to unwind(), must be string or object","messagePattern":"Invalid arg \"(.+?)\" to unwind\\(\\), must be string or object","errorType":"exception","errorClass":"MongooseError","httpStatus":null,"severity":"warning","filePath":"lib/aggregate.js","lineNumber":467,"sourceCode":" * @see $unwind https://www.mongodb.com/docs/manual/reference/aggregation/unwind/\n * @param {string|object|string[]|object[]} fields the field(s) to unwind, either as field names or as [objects with options](https://www.mongodb.com/docs/manual/reference/operator/aggregation/unwind/#document-operand-with-options). If passing a string, prefixing the field name with '$' is optional. If passing an object, `path` must start with '$'.\n * @return {Aggregate}\n * @api public\n */\n\nAggregate.prototype.unwind = function() {\n  const args = [...arguments];\n\n  const res = [];\n  for (const arg of args) {\n    if (arg && typeof arg === 'object') {\n      res.push({ $unwind: arg });\n    } else if (typeof arg === 'string') {\n      res.push({\n        $unwind: (arg[0] === '$') ? arg : '$' + arg\n      });\n    } else {\n      throw new MongooseError('Invalid arg \"' + arg + '\" to unwind(), ' +\n        'must be string or object');\n    }\n  }\n\n  return this.append.apply(this, res);\n};\n\n/**\n * Appends a new $replaceRoot operator to this aggregate pipeline.\n *\n * Note that the `$replaceRoot` operator requires field strings to start with '$'.\n * If you are passing in a string Mongoose will prepend '$' if the specified field doesn't start '$'.\n * If you are passing in an object the strings in your expression will not be altered.\n *\n * #### Example:\n *\n *     aggregate.replaceRoot(\"user\");\n *","sourceCodeStart":449,"sourceCodeEnd":485,"githubUrl":"https://github.com/Automattic/mongoose/blob/49cdab01366679723b487ecb754b38570f783289/lib/aggregate.js#L449-L485","documentation":"When a model class is compiled (or loadClass() runs), applyMethods copies schema.methods onto the model prototype. If a method name is in schema.reserved (save, remove, populate, overwrite, deleteOne, ... — the same reserved list Document uses), overwriting it on the prototype can break Mongoose internals, so Mongoose warns. The warning can be suppressed per method with a third options argument. Self-referencing a built-in (schema.method('save', Document.prototype.save)) is silently deleted instead, to support classes extending Document.","triggerScenarios":"schema.method('save', fn), schema.methods.deleteOne = fn, class methods named 'populate'/'remove'/'save' on a class passed to schema.loadClass(); defining a static-like helper on methods whose name matches a Document method without the suppressWarning option.","commonSituations":"Domain models that naturally want customSave/deleteOne semantics; porting classes with existing method names into Mongoose via loadClass; teams discovering collisions only after upgrading when the reserved list grew; wrapping save with pre-hooks instead would be the idiomatic fix.","solutions":["Rename the custom method (e.g. save -> saveDraft, deleteOne -> archive) and keep built-ins intact.","If you really intend to override, suppress explicitly: schema.method('save', fn, { suppressWarning: true }) — but test all internal call paths that rely on the original.","For behavior to run around save/delete, prefer middleware: schema.pre('save', fn) / schema.post('findOneAndDelete', fn) instead of shadowing methods.","With loadClass(), rename the class method or mark it static if it does not need an instance receiver."],"exampleFix":"// before\nschema.methods.save = function() { /* custom */ };\n\n// after\nschema.pre('save', function(next) { /* custom logic */ next(); });\n// or, if overriding deliberately:\nschema.method('save', function() { /* custom */ }, { suppressWarning: true });","handlingStrategy":"validation","validationCode":"function addMethod(schema, name, fn) {\n  if (schema.reserved && schema.reserved[name]) {\n    throw new Error(`Method \"${name}\" is reserved by Mongoose; rename it or pass { suppressWarning: true }`);\n  }\n  schema.method(name, fn);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Route all custom method registration through one helper that checks schema.reserved.","Prefer middleware (pre/post hooks) over shadowing built-in methods.","When using loadClass(), review class method names against the reserved list before import."],"tags":["mongoose","schema-methods","reserved-name","prototype-collision","warning"],"backgroundTag":"method-name-collision","analyzedSha":"49cdab01366679723b487ecb754b38570f783289","analyzedAt":"2026-08-21T22:54:00.882Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}