toeverything/AFFiNE · error · BlockSuiteError

ErrorCode.ModelCRUDError

ErrorCode.ModelCRUDError

Error message

cannot modify data in readonly mode

What it means

Store.addBlock is the only mutating API that hard-throws in readonly mode (updateBlock/deleteBlock/moveBlocks only console.error and return). A Store opened with readonly: true via the StoreContainer session options, or after setting doc.readonly = true, rejects every addBlock call with this ModelCRUDError to protect the shared Yjs doc.

Solutions

  1. Check doc.readonly (or subscribe to doc.readonly$) and disable/gate editing UI before calling addBlock
  2. If editing is intended, create the session without readonly or set doc.readonly = false first
  3. Audit components reused between reader and editor modes so mutations are conditional

Example fix

// before
doc.readonly = true;
doc.addBlock('affine:paragraph', {}, parentId); // -> cannot modify data in readonly mode

// after
if (!doc.readonly) {
  doc.addBlock('affine:paragraph', {}, parentId);
}
Defensive patterns

Strategy: validation

Validate before calling

if (!doc.readonly) {
  doc.addBlock(flavour, props, parentId);
} else {
  // request a writable session or surface a permission notice
}

Type guard

const isWritableDoc = (doc: Store): boolean => !doc.readonly;

Try / catch

try {
  doc.addBlock(flavour, props, parentId);
} catch (e) {
  if (e instanceof BlockSuiteError && e.code === ErrorCode.ModelCRUDError && doc.readonly) {
    // open a writable session instead of mutating the readonly one
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: doc.addBlock(...) on a store created with createSession({ id, readonly: true }) or after doc.readonly = true — common in preview/embed/reader modes or shared publication views.

Common situations: Editor mounted in read-only embed mode but edit toolbar still enabled; restoring a doc from a public share link where the session is readonly; stale component state pointing at a readonly session after a permission change.

Related errors


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

Appendix: source

Thrown at blocksuite/framework/store/src/model/store/store.ts:750

  /**
   * Creates and adds a new block to the store
   * @param flavour - The block's flavour (type)
   * @param blockProps - Optional properties for the new block
   * @param parent - Optional parent block or parent block ID
   * @param parentIndex - Optional index position in parent's children
   * @returns The ID of the newly created block
   * @throws {BlockSuiteError} When store is in readonly mode
   *
   * @category Block CRUD
   */
  addBlock<T extends BlockModel = BlockModel>(
    flavour: string,
    blockProps: Partial<(PropsOfModel<T> & BlockSysProps) | BlockProps> = {},
    parent?: BlockModel | string | null,
    parentIndex?: number
  ): string {
    if (this.readonly) {
      throw new BlockSuiteError(
        ErrorCode.ModelCRUDError,
        'cannot modify data in readonly mode'
      );
    }

    const id = blockProps.id ?? this._doc.workspace.idGenerator();

    this.transact(() => {
      this._crud.addBlock(
        id,
        flavour,
        { ...blockProps },
        typeof parent === 'string' ? parent : parent?.id,
        parentIndex
      );
    });

    return id;

View on GitHub (pinned to b4c8548c09)