siyuan-note/siyuan · error
invalid block structure
Error message
invalid block structure: %s [%s] cannot contain %s [%s]
What it means
invalidBlockContainmentError reports that a parent block of a given AST type cannot structurally contain the proposed child block. The tree validator (ValidateBlockPlacement / ValidateBlockReplacement) enforces SiYuan's document model constraints — e.g. a heading cannot have child nodes in the AST, a paragraph cannot contain blocks. The error includes both node types and IDs so the offending pair is identifiable.
Solutions
- Choose a legal parent: a container type (list item, blockquote, super block, document) for the child type
- For placing content under a heading, insert as a SIBLING after the heading (previousID = heading id) instead of using it as parentID
- Wrap the child in a structurally legal container if the target position requires nesting
Example fix
// before
await insertBlock({ parentID: headingID, data: '<div data-type="NodeParagraph">x</div>' }) // heading cannot contain
// after
await insertBlock({ previousID: headingID, data: '<div data-type="NodeParagraph">x</div>' }) // insert as sibling Defensive patterns
Strategy: validation
Validate before calling
const CONTAINER_TYPES = new Set(["list", "listitem", "blockquote", "super", "document"]);
if (!CONTAINER_TYPES.has(parentType)) throw new Error("parent is not a container"); Type guard
function isBlockNode(n) { return n != null && n.IsBlock === true && n.Type !== "NodeKramdownBlockIAL"; } Try / catch
try {
await insertBlock({ parentID, data });
} catch (e) {
if (String(e.message).startsWith("invalid block structure")) console.error("Illegal parent/child pair:", e.message);
else throw e;
} Prevention
- Know SiYuan block types before nesting: headings and paragraphs are leaves
- Use previousID for content that should follow a heading
- Fetch block type via getBlockInfo before choosing parentID
When it happens
Trigger: Inserting or replacing blocks where the target parent's AST type disallows the child type — e.g. passing parentID of a heading or paragraph as the container for a new child block via the editor transaction APIs or blockInsert.
Common situations: Plugins calling insert APIs with an arbitrary parentID (often a heading, expecting heading-child semantics); drag-and-drop moves computed by custom UI; programmatic document generation that nests blocks illegally.
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
- block [ ] is not a document that can declare a child…
- document [ ] cannot be pinned
- heading [ ] is a leaf block and cannot have children; to…
- --id is required
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/9c086c57710e9298.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/treenode/block_structure.go:236
continue
}
if nil == item.ListData {
item.ListData = &ast.ListData{}
}
item.ListData.Typ = 1
item.ListData.Num = num
item.ListData.Delimiter = delimiter
item.ListData.Marker = []byte(strconv.Itoa(num) + string(delimiter))
num++
}
}
func isContentBlock(node *ast.Node) bool {
return nil != node && node.IsBlock() && ast.NodeKramdownBlockIAL != node.Type
}
func invalidBlockContainmentError(parent, child *ast.Node) error {
return fmt.Errorf("invalid block structure: %s [%s] cannot contain %s [%s]",
parent.Type.String(), parent.ID, child.Type.String(), child.ID)
}
func invalidBlockNodeError(node *ast.Node) error {
if nil == node {
return fmt.Errorf("invalid block structure: block node is nil")
}
return fmt.Errorf("invalid block structure: %s [%s] is not a content block", node.Type.String(), node.ID)
}
View on GitHub (pinned to 9f775e8a12)