siyuan-note/siyuan · error

document cannot be deleted as a block

Error message

document cannot be deleted as a block [%s]

What it means

PerformBlockOperation explicitly rejects deleting the document root node as if it were an ordinary block: when the resolved node is the tree's Root, it returns "document cannot be deleted as a block [%s]". Document removal must go through the document API instead of the block-transaction path.

Solutions

  1. Filter out document-type blocks (root IDs) before issuing delete block operations.
  2. Use /api/filetree/removeDoc for document deletion instead of the block operation API.
  3. Check the block's type from /api/block/getBlockInfo (data.root_id / type 'd') and branch accordingly.

Example fix

// before
rows.forEach(r => deleteBlock(r.id)); // includes type 'd' rows
// after
rows.filter(r => r.type !== 'd').forEach(r => deleteBlock(r.id));
rows.filter(r => r.type === 'd').forEach(r => fetchPost('/api/filetree/removeDoc', { notebook: r.box, path: r.path }));
Defensive patterns

Strategy: validation

Validate before calling

async function isDocumentBlock(id) {
  const res = await fetchPost('/api/query/sql', { stmt: `SELECT type, root_id, path, box FROM blocks WHERE id='${id.replace(/'/g, "''")}'` });
  return res.data[0] && res.data[0].type === 'd';
}

Try / catch

try { await deleteBlock(id); } catch (e) { if (String(e.msg).includes('document cannot be deleted as a block')) await fetchPost('/api/filetree/removeDoc', { notebook: box, path }); else throw e; }

Prevention

When it happens

Trigger: Calling PerformBlockOperation with Action="delete" and an ID that is the document (root) block ID — typically the rootID of a .sy file rather than a child block ID.

Common situations: Scripts iterating over /api/query/sql results that include root blocks (type 'd') and deleting every row; code that captured the document ID instead of a paragraph/heading ID from a selection.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/366ce70a5fd518d2. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/block_operation.go:49

	switch operation.Action {
	case "appendInsert", "insert":
		if operation.Action == "appendInsert" || operation.PreviousID == "" && operation.NextID == "" {
			if err = treenode.CheckContainerParent(operation.ParentID); err != nil {
				return nil, err
			}
		}
	case "delete":
		// 外部删除请求必须命中现存节点,编辑器内部仍可使用幂等删除。
		tree, loadErr := LoadTreeByBlockID(operation.ID)
		if loadErr != nil {
			return nil, loadErr
		}
		node := treenode.GetNodeInTree(tree, operation.ID)
		if node == nil {
			return nil, fmt.Errorf("block not found [%s]", operation.ID)
		}
		if node == tree.Root {
			return nil, fmt.Errorf("document cannot be deleted as a block [%s]", operation.ID)
		}
	default:
		return nil, fmt.Errorf("unsupported block operation [%s]", operation.Action)
	}

	tx := &Transaction{DoOperations: []*Operation{operation}}
	if err = performTxSyncLocked(tx); err != nil {
		return nil, err
	}
	return []*Transaction{tx}, nil
}

View on GitHub (pinned to 9f775e8a12)