siyuan-note/siyuan · error
heading [ ] is a leaf block and cannot have children; to…
Error message
heading [%s] is a leaf block and cannot have children; to place a block below this heading, pass previousID=<heading id> or previousID=<last block below the heading> instead of parentID
What it means
In SiYuan's data model a heading is a leaf block — content that visually appears 'under' a heading is actually stored as subsequent sibling nodes (computed by HeadingChildren), not AST children. CheckContainerParent rejects using a heading as parentID and instructs the caller to position with previousID instead. The long message documents the exact remedy.
Solutions
- Pass previousID = the heading ID (new block becomes the sibling right below the heading) instead of parentID
- To append after all content under the heading, compute the last block below it (HeadingChildren semantics) and use that as previousID
- If you truly need nesting, use a container type (super block, list item, blockquote) rather than a heading
Example fix
// before
await insertBlock({ parentID: headingID, data }) // rejected: heading is a leaf
// after
await insertBlock({ previousID: headingID, data }) // sibling below the heading Defensive patterns
Strategy: validation
Validate before calling
const parent = await getBlockInfo(parentID);
if (parent && parent.type === "h") {
options.previousID = parentID; delete options.parentID; // heading is a leaf: switch to sibling placement
} Type guard
function isHeading(info) { return info?.type === "h"; } Prevention
- Remember: heading children in the UI are stored as siblings — never pass headings as parentID
- Use previousID for anything meant to appear under a heading
- When building drop targets, expand headings to their HeadingChildren and target the last child instead
When it happens
Trigger: blockInsert/blockAppend with parentID = a heading ID; validateBlockMove targeting a heading as the new parent; plugins that assume headings behave like containers and pass heading IDs as parents.
Common situations: Outliner-style plugins inserting notes 'under' headings; scripts migrating outlines that treat headings as folders; move operations whose drop target was computed as a heading container.
Understand the failure class
Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.
Related errors
- invalid block structure
- invalid block structure
- parent block not found
- Access to encrypted notebook data is not supported via this…
- AI editor action must not be empty
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/0c35c8af9cbdd6dd.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/treenode/blocktree.go:528
return false
}
// CheckContainerParent 校验 parentID 指向的块是否允许接收子块。
// 仅在“通过 parentID 定位插入/移动目标”(即不依赖 previousID/nextID)的场景下调用,
// 因为一旦带 previousID/nextID,事务层走的是兄弟级 InsertAfter/InsertBefore,天然合法。
// 返回 nil 表示合法;返回 error 时调用方应拒绝本次操作。
func CheckContainerParent(parentID string) error {
bt := GetBlockTree(parentID)
if nil == bt {
return fmt.Errorf("parent block not found: %s", parentID)
}
if IsContainerType(bt.Type) {
return nil
}
if "h" == bt.Type {
// 标题是叶子块,其“子内容”在数据结构上实为后续兄弟节点(由 HeadingChildren 按层级推算)。
// 把块挂成标题的 AST 子节点属于非法嵌套,应改用 previousID 定位。
return fmt.Errorf("heading [%s] is a leaf block and cannot have children; to place a block below this heading, pass previousID=<heading id> or previousID=<last block below the heading> instead of parentID", parentID)
}
return fmt.Errorf("block [%s] type %q is a leaf block and cannot have children; use previousID to place the block as its sibling instead", parentID, bt.Type)
}
// CheckListItemNesting 校验 parentID 和 childID 是否形成“列表项直含列表项”的非法嵌套。
// 嵌套列表的正确结构是 ListItem > List > ListItem,列表项不能直接作为另一个列表项的子块。
// 仅在 move 场景调用(源和目标类型均已知)。
func CheckListItemNesting(parentID, childID string) error {
parentBt := GetBlockTree(parentID)
childBt := GetBlockTree(childID)
if nil == parentBt || nil == childBt {
return nil // 查不到就放行,不阻塞未知场景
}
if "i" == parentBt.Type && "i" == childBt.Type {
return fmt.Errorf("a list-item cannot directly contain another list-item; to nest, first create a list (NodeList) under the outer list-item, then add the inner list-items to that list")
}
return nil
}View on GitHub (pinned to 9f775e8a12)