toeverything/AFFiNE · error · SchemaValidateError
Root block cannot have parent.
Error message
Root block cannot have parent.
What it means
Schema.validate refuses any schema whose model.role === 'root' when a parentFlavour is supplied. Root blocks (for example a doc root like affine:page) are top-level by definition, so nesting one under another block is an invalid hierarchy and validation stops immediately.
Source
Thrown at blocksuite/framework/store/src/schema/schema.ts:80
): void => {
const schema = this.flavourSchemaMap.get(flavour);
if (!schema) {
throw new SchemaValidateError(flavour, SCHEMA_NOT_FOUND_MESSAGE);
}
const validateChildren = () => {
childFlavours?.forEach(childFlavour => {
const childSchema = this.flavourSchemaMap.get(childFlavour);
if (!childSchema) {
throw new SchemaValidateError(childFlavour, SCHEMA_NOT_FOUND_MESSAGE);
}
this.validateSchema(childSchema, schema);
});
};
if (schema.model.role === 'root') {
if (parentFlavour) {
throw new SchemaValidateError(
schema.model.flavour,
'Root block cannot have parent.'
);
}
validateChildren();
return;
}
if (!parentFlavour) {
throw new SchemaValidateError(
schema.model.flavour,
'None root block must have parent.'
);
}
const parentSchema = this.flavourSchemaMap.get(parentFlavour);
if (!parentSchema) {View on GitHub (pinned to b4c8548c09)
Solutions
- Create root blocks without a parent: doc.addBlock(rootFlavour).
- Use a non-root container flavour when you need nesting under other blocks.
- When importing snapshots, drop parent ids attached to root-role blocks.
Example fix
// before
await doc.addBlock('affine:page', {}, parentId); // root under a parent
// after
await doc.addBlock('affine:page'); // root blocks are added without a parent Defensive patterns
Strategy: validation
Validate before calling
if (doc.schema.get(flavour)?.model.role === 'root') {
await doc.addBlock(flavour); // root: never pass a parent
} else {
await doc.addBlock(flavour, props, parentId);
} Type guard
const isRootFlavour = (schema: Schema, flavour: string): boolean => schema.get(flavour)?.model.role === 'root';
Try / catch
try {
await doc.addBlock(flavour, props, parentId);
} catch (e) {
if (e instanceof SchemaValidateError && e.message.includes('Root block cannot have parent')) {
// retry without parent
await doc.addBlock(flavour);
} else throw e;
} Prevention
- Treat role === 'root' blocks as strictly top-level in insertion and re-parenting code.
- Drop parent ids from snapshot nodes whose flavour has root role before import.
When it happens
Trigger: doc.addBlock('affine:page', {}, someParentId) — creating a root-flavoured block under another block; moving a root model into a container; snapshot import that places a root block under a parent.
Common situations: Treating the page/root flavour as a generic container; hand-built snapshots with wrong parent links; copy-paste logic that re-parents whatever block was copied, including roots.
Related errors
- Root block cannot have parent: ${parentFlavour}.
- None root block must have parent.
- Block cannot have parent: ${parent.model.flavour}.
- ErrorCode.NoRootModelError
- ErrorCode.ModelCRUDError
AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18).
Data as JSON: /api/errors/ed05423fb1cd868a.
Report an issue: GitHub.