siyuan-note/siyuan · error
block [ ] type is a leaf block and cannot have children…
Error message
block [%s] type %q is a leaf block and cannot have children; use previousID to place the block as its sibling instead
What it means
CheckContainerParent validates that a parentID given to insert/append/move operations points to a container block. Some block types (headings 'h' and other leaf blocks) cannot structurally hold children in the AST, so passing one as parentID would create illegal nesting. The kernel rejects the operation and tells you to use previousID instead so the block becomes a sibling positioned after the leaf.
Solutions
- Replace parentID=<heading id> with previousID=<heading id> so the new block is placed directly below the heading as a sibling.
- If the block must go after existing content below the heading, pass previousID=<id of the last block currently below the heading>.
- Fetch the target block's type first (GetBlockTree) and only use parentID for container types; otherwise use previousID.
Example fix
// before
insertBlock({dataType: "markdown", data: "para", parentID: headingID})
// after
insertBlock({dataType: "markdown", data: "para", previousID: headingID}) Defensive patterns
Strategy: validation
Validate before calling
const bt = await fetchPost('/api/block/getBlockInfo', {id: headingID});
if (bt.data.type === 'h') { /* use previousID instead of parentID */ } Prevention
- Treat headings, paragraphs, and other leaf blocks as siblings-only targets; reserve parentID for containers.
- Check block type via getBlockInfo before choosing parentID vs previousID.
- When inserting 'below' anything, default to previousID.
When it happens
Trigger: Calling blockInsert/blockAppend/blockPrepend, assetCreateHTML, or validateBlockMove with parentID set to a heading block or any leaf block type (paragraphs, headings, etc.) instead of a container such as a document, blockquote, or list item.
Common situations: Scripts or plugins that want content 'under a heading' pass the heading's ID as parentID; in SiYuan's data model that content is a following sibling, not a child. Common when migrating code from other outliners or when a user copies a heading ID from the UI.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- a list-item cannot directly contain another list-item; to…
- asset path must be absolute
- block is not a list item
- block is not a task list item
- block not found
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/d405a82bc960343c.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/treenode/blocktree.go:530
// 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
}
func SetBlockTreePath(tree *parse.Tree) {View on GitHub (pinned to 9f775e8a12)