toeverything/AFFiNE · error · SchemaValidateError

None root block must have parent.

Error message

None root block must have parent.

What it means

The counterpart of the root rule: Schema.validate throws when a block whose model.role is not 'root' is validated without a parentFlavour. Every non-root block must live under a parent, so validating or adding one standalone is an invalid hierarchy.

Source

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

        }
        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) {
      throw new SchemaValidateError(parentFlavour, SCHEMA_NOT_FOUND_MESSAGE);
    }
    this.validateSchema(schema, parentSchema);
    validateChildren();
  };

  /**
   * Returns an object mapping each registered flavour to its version number.
   */
  get versions() {
    return Object.fromEntries(

View on GitHub (pinned to b4c8548c09)

Solutions

  1. Pass the parent block id: doc.addBlock('affine:paragraph', {}, noteModel.id).
  2. Ensure the doc has a root and an intermediate container before adding children (usually via doc.load()).
  3. If the block is intentionally top-level, its schema must declare model.role = 'root'.

Example fix

// before
await doc.addBlock('affine:paragraph'); // no parent

// after
const noteId = doc.root?.children[0]?.id;
await doc.addBlock('affine:paragraph', {}, noteId);
Defensive patterns

Strategy: validation

Validate before calling

const role = doc.schema.get(flavour)?.model.role;
if (role && role !== 'root' && !parentId) {
  throw new Error(`flavour ${flavour} requires a parent block id`);
}
await doc.addBlock(flavour, props, parentId);

Type guard

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

Try / catch

try {
  await doc.addBlock(flavour);
} catch (e) {
  if (e instanceof SchemaValidateError && e.message.includes('must have parent')) {
    await doc.addBlock(flavour, props, resolvedParentId);
  } else throw e;
}

Prevention

When it happens

Trigger: doc.addBlock('affine:paragraph') with no parent id passed; validating a child flavour with schema.validate(flavour) and no second argument; snapshot import where the parent block was dropped.

Common situations: Forgetting the parent argument in addBlock during prototyping; docs loaded from partial snapshots that lost the root; constructing test hierarchies by hand.

Related errors


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