{"record":{"id":"df2751d477cd5d6b","repo":"Automattic/mongoose","slug":"refpath-must-be-a-string-or-a-function-that-retu","errorCode":null,"errorMessage":"`refPath` must be a string or a function that returns a string, got ${inspect(refPath)}","messagePattern":"`refPath` must be a string or a function that returns a string, got (.+?)","errorType":"exception","errorClass":"MongooseError","httpStatus":null,"severity":"error","filePath":"lib/document.js","lineNumber":1465,"sourceCode":"      }\n\n      // Check refPath\n      let refPath = schema.options.refPath;\n      if (refPath == null) {\n        return false;\n      }\n\n      if (typeof refPath === 'function' && !refPath[modelSymbol]) {\n        let fullPath = path;\n        const fullPathWithIndexes = this.$__fullPathWithIndexes?.();\n        if (fullPathWithIndexes?.length) {\n          fullPath = fullPathWithIndexes + '.' + path;\n        }\n        refPath = refPath.call(this, this, fullPath);\n      }\n\n      if (typeof refPath !== 'string') {\n        throw new MongooseError('`refPath` must be a string or a function that returns a string, got ' + inspect(refPath));\n      }\n\n      const modelName = this.ownerDocument().get(refPath);\n      return modelName === model.modelName || modelName === model.baseModelName;\n    })();\n\n    let didPopulate = false;\n    if (refMatches && val instanceof Document && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) {\n      const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id;\n      this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor });\n      val.$__.wasPopulated = { value: unpopulatedValue };\n      didPopulate = true;\n    }\n\n    let popOpts;\n    const typeKey = this.$__schema.options.typeKey;\n    if (schema.options &&\n        Array.isArray(schema.options[typeKey]) &&","sourceCodeStart":1447,"sourceCodeEnd":1483,"githubUrl":"https://github.com/Automattic/mongoose/blob/49cdab01366679723b487ecb754b38570f783289/lib/document.js#L1447-L1483","documentation":"MongooseError raised while $set processes a path with a dynamic `refPath`: to decide whether an assigned populated document matches the ref, Mongoose resolves the refPath option. It must be the string name of a field, or a function that returns a string; a number, undefined, or any other type throws immediately.","triggerScenarios":"A schema option `refPath` set to a non-string (e.g. a number or the field value itself); or a refPath function returning undefined - typically reading a discriminator/type field that is not set - when you then assign a populated document to the path (`doc.item = someDoc`) or populate it.","commonSituations":"Polymorphic references where the type field is optional or not yet set; refPath functions like `function() { return this.type; }` invoked before the doc has a type; copy-paste mistakes pointing refPath at a data field instead of a model-name field.","solutions":["Make refPath the string name of a field that always contains a model name","If refPath is a function, guarantee a string return: `function() { return this.type || 'DefaultModel'; }`","Set the type/discriminator field before assigning or populating the refPath path","Skip the assignment/populate when the refPath field is missing"],"exampleFix":"// before\nconst schema = new Schema({\n  type: String,\n  item: { type: Schema.Types.ObjectId, refPath: function() { return this.type; } } // undefined when type unset\n});\ndoc.item = populatedDoc; // refPath resolved to undefined -> MongooseError\n\n// after\nitem: { type: Schema.Types.ObjectId, refPath: function() { return this.type || 'Product'; } }","handlingStrategy":"validation","validationCode":"// Ensure refPath resolves before assigning a populated doc\nconst st = doc.constructor.schema.path('item');\nconst rp = st.options.refPath;\nconst fieldName = typeof rp === 'function' ? rp.call(doc, doc, 'item') : rp;\nif (typeof fieldName !== 'string' || typeof doc.get(fieldName) !== 'string') {\n  throw new Error('refPath does not resolve to a model name; set the type field first');\n}\ndoc.item = populatedDoc;","typeGuard":"function resolvesToModelName(doc, refPathOpt) {\n  const v = typeof refPathOpt === 'function' ? refPathOpt.call(doc, doc) : refPathOpt;\n  return typeof v === 'string' && typeof doc.get(v) === 'string';\n}","tryCatchPattern":"try {\n  doc.item = populatedDoc;\n} catch (err) {\n  if (/refPath` must be a string/.test(err.message)) {\n    // the discriminator/type field is unset; set it (or a default model) before assigning\n  } else { throw err; }\n}","preventionTips":["Give the refPath source field an enum and a default so it always resolves","Prefer a plain string refPath over a function unless the indirection is required","Set polymorphic type fields before populate() or populated-doc assignment"],"tags":["mongoose","refpath","populate","dynamic-refs","polymorphic"],"backgroundTag":"refpath-invalid-value","analyzedSha":"49cdab01366679723b487ecb754b38570f783289","analyzedAt":"2026-08-21T22:54:00.882Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}