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

  1. Choose a legal parent: a container type (list item, blockquote, super block, document) for the child type
  2. For placing content under a heading, insert as a SIBLING after the heading (previousID = heading id) instead of using it as parentID
  3. 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

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


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)