toeverything/AFFiNE · error · SchemaValidateError

Block cannot have parent: ${parent.model.flavour}.

Error message

Block cannot have parent: ${parent.model.flavour}.

What it means

validateSchema calls _validateParent(child, parent), which matches the child against the parent's model.children allow-list (default ['*']) and the parent against the child's model.parent list (default ['*']), including minimatch wildcards and '@role' syntax. If no combination matches, the pairing is rejected with 'Block cannot have parent: ...'. This is the general 'this child is not allowed under that parent' error.

Source

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

      )
    );
  }

  /**
   * Validates the relationship between a child and parent schema.
   * Throws if the relationship is invalid.
   *
   * @param child - The child block schema.
   * @param parent - The parent block schema.
   * @throws {SchemaValidateError} If the relationship is invalid.
   */
  validateSchema(child: BlockSchemaType, parent: BlockSchemaType) {
    this._validateRole(child, parent);

    const relationCheckSuccess = this._validateParent(child, parent);

    if (!relationCheckSuccess) {
      throw new SchemaValidateError(
        child.model.flavour,
        `Block cannot have parent: ${parent.model.flavour}.`
      );
    }
  }
}

View on GitHub (pinned to b4c8548c09)

Solutions

  1. Add the child flavour (or '*' / a matching '@role') to the parent schema's model.children list.
  2. Add the parent flavour to the child schema's model.parent list.
  3. Choose a compatible parent container for the child you are inserting.
  4. Pre-check with doc.schema.isValid(childFlavour, parentFlavour), which wraps validateSchema without throwing.

Example fix

// before
// container schema: { flavour: 'my:box', role: 'hub', children: ['affine:note'] }
await doc.addBlock('affine:paragraph', {}, boxId); // rejected

// after
// container schema: { flavour: 'my:box', role: 'hub', children: ['affine:note', 'affine:paragraph'] }
await doc.addBlock('affine:paragraph', {}, boxId);
Defensive patterns

Strategy: validation

Validate before calling

if (!doc.schema.isValid(childFlavour, parentFlavour)) {
  throw new Error(`${childFlavour} is not allowed under ${parentFlavour}`);
}
await doc.addBlock(childFlavour, props, parentId);

Type guard

const isAllowedChild = (schema: Schema, child: string, parent: string): boolean =>
  schema.isValid(child, parent);

Try / catch

try {
  await doc.addBlock(childFlavour, props, parentId);
} catch (e) {
  if (e instanceof SchemaValidateError && e.message.includes('Block cannot have parent')) {
    // pick a compatible parent from schema.get(parentFlavour).model.children
  } else throw e;
}

Prevention

When it happens

Trigger: Parent schema declares children: ['affine:note'] but you addBlock an affine:paragraph under it; child declares parent: ['affine:note'] but is added under a different container; role tokens ('@hub') that do not match the actual roles.

Common situations: Custom container blocks with restrictive children lists; refactors that move blocks under new containers without updating parent/children constraints; role-based schemas where a block's role changed.

Related errors


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