{"record":{"id":"5e5925cc3d74492b","repo":"Automattic/mongoose","slug":"cannot-validate-subdocument-that-does-not-have-a-p","errorCode":null,"errorMessage":"Cannot validate subdocument that does not have a parent","messagePattern":"Cannot validate subdocument that does not have a parent","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"lib/document.js","lineNumber":3175,"sourceCode":"        paths.add(modifiedPath);\n      }\n    }\n\n    for (const subdoc of topLevelSubdocs) {\n      if (subdoc.$basePath) {\n        const fullPathToSubdoc = subdoc.$__pathRelativeToParent();\n\n        // Remove child paths for now, because we'll be validating the whole\n        // subdoc.\n        // The following is a faster take on looping through every path in `paths`\n        // and checking if the path starts with `fullPathToSubdoc` re: gh-13191\n        for (const modifiedPath of subdoc.modifiedPaths()) {\n          paths.delete(fullPathToSubdoc + '.' + modifiedPath);\n        }\n\n        const subdocParent = subdoc.$parent();\n        if (subdocParent == null) {\n          throw new Error('Cannot validate subdocument that does not have a parent');\n        }\n        if (doc.$isModified(fullPathToSubdoc, null, modifiedPaths) &&\n              // Avoid using isDirectModified() here because that does additional checks on whether the parent path\n              // is direct modified, which can cause performance issues re: gh-14897\n              !Object.hasOwn(subdocParent.$__.activePaths.getStatePaths('modify'), fullPathToSubdoc) &&\n              !subdocParent.$isDefault(fullPathToSubdoc)) {\n          paths.add(fullPathToSubdoc);\n\n          if (doc.$__.pathsToScopes == null) {\n            doc.$__.pathsToScopes = {};\n          }\n          doc.$__.pathsToScopes[fullPathToSubdoc] = subdoc.$isDocumentArrayElement ?\n            subdoc.__parentArray :\n            subdoc.$parent();\n\n          doValidateOptions[fullPathToSubdoc] = { skipSchemaValidators: true };\n          if (subdoc.$isDocumentArrayElement && subdoc.__index != null) {\n            doValidateOptions[fullPathToSubdoc].index = subdoc.__index;","sourceCodeStart":3157,"sourceCodeEnd":3193,"githubUrl":"https://github.com/Automattic/mongoose/blob/49cdab01366679723b487ecb754b38570f783289/lib/document.js#L3157-L3193","documentation":"During validate(), Mongoose walks modified subdocuments and validates each through its parent. If a subdocument still has modified paths but `$parent()` returns null - it was detached from its owning document - Mongoose cannot route the validation and throws this Error.","triggerScenarios":"Splicing/removing an element from a document array while it is still tracked as modified; replacing a single-embedded subdocument and then validating; constructing subdocuments standalone (`new Subdoc()`) and mixing them into a parent; moving a subdocument instance between documents.","commonSituations":"Array manipulation (pull/splice/set) followed by validate()/save() hitting corner-case bugs in some Mongoose 8.x releases; application code that reuses one subdoc instance across parents; detached subdoc tracking regressions that were fixed across minor versions.","solutions":["Create subdocuments through the parent (`parent.arr.push({...})`, `parent.arr.create(...)`) instead of standalone instances","When moving data between documents, clone with toObject() rather than reusing the subdocument instance","Upgrade Mongoose - multiple detached-subdoc tracking fixes have shipped","Ensure removed elements are fully detached before triggering validation"],"exampleFix":"// before\nconst sub = new ItemsSubdoc({ name: 'x' }); // standalone subdocument\ndoc.items.push(sub);\nawait doc.validate(); // subdoc may have no resolvable parent -> Error\n\n// after (let the parent build the subdocument)\ndoc.items.push({ name: 'x' });\nawait doc.validate();","handlingStrategy":"try-catch","validationCode":"// Reject detached subdocs before validating\nfor (const sub of doc.$getAllSubdocs()) {\n  if (sub.$parent() == null && sub.modifiedPaths().length > 0) {\n    throw new Error('A modified subdocument is detached from its parent; re-attach or remove it');\n  }\n}\nawait doc.validate();","typeGuard":null,"tryCatchPattern":"try {\n  await doc.validate();\n} catch (err) {\n  if (err.message === 'Cannot validate subdocument that does not have a parent') {\n    // a modified subdoc got detached (splice/set/move); rebuild it via the parent arrays\n  } else { throw err; }\n}","preventionTips":["Create subdocuments via parent arrays (push/create) instead of new Subdoc()","Clone subdocuments with toObject() when copying between parents; never move instances","Keep Mongoose current - detached-subdoc tracking has multiple fixes across 8.x"],"tags":["mongoose","subdocuments","validate","parent-child"],"backgroundTag":"orphaned-subdocument","analyzedSha":"49cdab01366679723b487ecb754b38570f783289","analyzedAt":"2026-08-21T22:54:00.882Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}