{"record":{"id":"7f6f597307af00ca","repo":"siyuan-note/siyuan","slug":"sort-target-id-is-required-when-sort-position-is-s","errorCode":null,"errorMessage":"sort target ID is required when sort position is specified","messagePattern":"sort target ID is required when sort position is specified","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/file.go","lineNumber":1298,"sourceCode":"\tcreateDocLock.Lock()\n\tdefer createDocLock.Unlock()\n\n\tbox, err := getOpenedBox(boxID)\n\tif nil != err {\n\t\treturn\n\t}\n\tsortTargetID, _ := arg[\"sortTargetID\"].(string)\n\tsortPosition, _ := arg[\"sortPosition\"].(string)\n\tif \"\" != sortTargetID {\n\t\tif \"before\" != sortPosition && \"after\" != sortPosition {\n\t\t\treturn nil, fmt.Errorf(\"invalid sort position [%s]\", sortPosition)\n\t\t}\n\t\ttarget := treenode.GetBlockTree(sortTargetID)\n\t\tif !isSortableDocument(target) || target.BoxID != boxID || path.Dir(target.Path) != path.Dir(p) {\n\t\t\treturn nil, fmt.Errorf(\"sort target document [%s] is not a sibling of the new document\", sortTargetID)\n\t\t}\n\t} else if \"\" != sortPosition {\n\t\treturn nil, errors.New(\"sort target ID is required when sort position is specified\")\n\t}\n\n\tluteEngine := util.NewLute()\n\tluteEngine.SetHTMLTag2TextMark(true)\n\tdom := luteEngine.Md2BlockDOM(md, false)\n\ttree, err = createDoc(box.ID, p, title, dom, false)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tFlushTxQueue()\n\tif \"\" != sortTargetID {\n\t\tif _, sortErr := ReorderDocs([]string{tree.ID}, sortTargetID, sortPosition); nil != sortErr {\n\t\t\tlogging.LogErrorf(\"reorder created document [%s] failed: %s\", tree.ID, sortErr)\n\t\t\tbox.setSortByConf(path.Dir(tree.Path), tree.ID)\n\t\t}\n\t} else if 0 < len(sorts) {\n\t\tChangeFileTreeSort(box.ID, sorts)","sourceCodeStart":1280,"sourceCodeEnd":1316,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/file.go#L1280-L1316","documentation":"CreateDocByMd in kernel/model/file.go:1298 lets callers position a new document relative to an existing sibling document. The sort target ID (sortTargetID) and the sort position (sortPosition) are a paired argument set: a position is meaningless without the reference document it is relative to. When a sortPosition is supplied but sortTargetID is empty, the function rejects the request instead of guessing a placement.","triggerScenarios":"Calling CreateDocByMd (or the /api/filetree/createDocWithMdByPosition-style endpoints that wrap it) with a non-empty sortPosition (e.g. 'before', 'after', or an index) while leaving the sortTargetID/sortTarget argument empty string.","commonSituations":"Plugin or automation scripts that build the request payload programmatically and include sortPosition copied from a template but never set the target ID; frontends that compute position from a drag-drop but lose the dragged-over document's ID; API consumers porting code from an older SiYuan version where position could be specified standalone.","solutions":["Pass the ID of the sibling document the new doc should be placed relative to (sortTargetID) together with sortPosition.","If you do not need positional placement, omit sortPosition entirely and pass the parent path (hPath/parentID) instead.","Verify the target ID is a valid, sortable sibling document in the same notebook and same parent path (the next validation after this one checks isSortableDocument, BoxID, and sibling dir)."],"exampleFix":"// before\ncreateDocByMd(boxID, hPath, md, {sortPosition: \"after\"});\n// after\ncreateDocByMd(boxID, hPath, md, {sortPosition: \"after\", sortTargetID: \"20240101120000-abcdefg\"});","handlingStrategy":"validation","validationCode":"if (sortPosition && !sortTargetID) {\n  throw new Error(\"sortTargetID is required when sortPosition is set\");\n}\nconst target = await getBlockTreeByID(sortTargetID); // must exist, be sortable, same notebook & parent dir","typeGuard":"function hasValidSortTarget(args) {\n  return !args.sortPosition || (typeof args.sortTargetID === \"string\" && args.sortTargetID.length > 0);\n}","tryCatchPattern":"try {\n  await createDocByMd(boxID, hPath, md, {sortPosition, sortTargetID});\n} catch (e) {\n  if (String(e.msg).includes(\"sort target ID is required\")) {\n    await createDocByMd(boxID, hPath, md, {}); // fall back to default placement\n  } else { throw e; }\n}","preventionTips":["Treat sortTargetID and sortPosition as an atomic pair; never send one without the other.","Derive sortTargetID from the actual DOM/tree node that the user dropped onto.","Add a payload schema check in client code before issuing the API call."],"tags":["api","go","validation","document-creation"],"backgroundTag":"missing-required-argument","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}