toeverything/AFFiNE · error · SchemaValidateError

Root block cannot have parent: ${parentFlavour}.

Error message

Root block cannot have parent: ${parentFlavour}.

What it means

Thrown by the private _validateRole during validateSchema(child, parent): any child schema whose model.role === 'root' is rejected under any parent, with the offending parent flavour named in the message. You reach this path through validate()'s validateChildren (a root block listed among childFlavours) or through direct pairwise checks like schema.isValid / validateSchema.

Source

Thrown at blocksuite/framework/store/src/schema/schema.ts:243

      });
    });
  }

  /**
   * Validates the role relationship between child and parent schemas.
   * Throws if the child is a root block but has a parent.
   *
   * @param child - The child block schema.
   * @param parent - The parent block schema.
   * @throws {SchemaValidateError} If the child is a root block with a parent.
   */
  private _validateRole(child: BlockSchemaType, parent: BlockSchemaType) {
    const childRole = child.model.role;
    const childFlavour = child.model.flavour;
    const parentFlavour = parent.model.flavour;

    if (childRole === 'root') {
      throw new SchemaValidateError(
        childFlavour,
        `Root block cannot have parent: ${parentFlavour}.`
      );
    }
  }

  /**
   * Checks if the child flavour is valid under the parent flavour.
   *
   * @param child - The child block flavour name.
   * @param parent - The parent block flavour name.
   * @returns True if the relationship is valid, false otherwise.
   */
  isValid(child: string, parent: string) {
    const childSchema = this.flavourSchemaMap.get(child);
    const parentSchema = this.flavourSchemaMap.get(parent);
    if (!childSchema || !parentSchema) {
      return false;

View on GitHub (pinned to b4c8548c09)

Solutions

  1. Remove root-role flavours from children lists and never pass them as children.
  2. Guard with schema.get(flavour)?.model.role !== 'root' before treating a block as a child.
  3. Use schema.isValid(childFlavour, parentFlavour) (non-throwing) to filter candidate children.

Example fix

// before
doc.schema.validate('affine:note', undefined, ['affine:page']); // root as child

// after
doc.schema.validate('affine:note', undefined, ['affine:paragraph']);
Defensive patterns

Strategy: validation

Validate before calling

if (doc.schema.get(childFlavour)?.model.role === 'root') {
  throw new Error(`${childFlavour} is a root block and cannot be a child`);
}
doc.schema.validate(parentFlavour, undefined, [childFlavour]);

Type guard

const canBeChild = (schema: Schema, flavour: string): boolean =>
  schema.get(flavour)?.model.role !== 'root';

Try / catch

if (!doc.schema.isValid(childFlavour, parentFlavour)) {
  // skip or re-target: isValid wraps validateSchema without throwing
}

Prevention

When it happens

Trigger: schema.validate(parent, undefined, ['affine:page']) — a root flavour listed as a child; validateSchema(rootSchema, parentSchema) invoked by hierarchy tooling; addBlock of a root-flavoured block under a parent via code paths that use validateSchema.

Common situations: Copy-paste or drag-drop logic that accepts any block as a child, including roots; schema children lists written by hand that accidentally include a root flavour.

Related errors


AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18). Data as JSON: /api/errors/6eca6c4c7f2357b8. Report an issue: GitHub.