toeverything/AFFiNE · error · BlockSuiteError
ErrorCode.ModelCRUDError
ErrorCode.ModelCRUDError
Error message
cannot modify data in readonly mode
What it means
Thrown by Store.addBlock when this.readonly is true. A readonly store is a doc opened for read-only access (e.g. viewer mode, snapshot inspection); mutating it would desync viewers and could break collaboration guarantees, so every write API refuses up front.
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 26c515e050)
Solutions
- Check store.readonly before issuing mutations and disable/skip the action in the UI.
- Open the doc in read-write mode if editing is intended: pass readonly:false when creating the session.
- Guard global edit handlers (keyboard, toolbar) with an if (store.readonly) return;.
- If readonly was set due to a transient condition (permissions, connection), re-open the doc writable once it clears.
Example fix
// before
store.addBlock('affine:paragraph', {}, parent);
// after
if (store.readonly) {
notifyUser('Document is read-only');
return;
}
store.addBlock('affine:paragraph', {}, parent); Defensive patterns
Strategy: validation
Validate before calling
export function assertWritable(store: Store): void {
if (store.readonly) {
throw new Error('Store is read-only; mutation rejected');
}
}
assertWritable(store);
store.addBlock('affine:paragraph', {}, parent); Type guard
export const isWritable = (store: Store): boolean => !store.readonly;
Try / catch
try {
store.addBlock(flavour, props, parent);
} catch (e) {
if (e instanceof BlockSuiteError && e.code === ErrorCode.ModelCRUDError && /readonly mode/.test(e.message)) {
notifyReadonlyMode(); // or re-open the doc writable
} else throw e;
} Prevention
- Check store.readonly at the UI layer and disable edit controls.
- Bind edit handlers (keyboard, toolbar) behind a writable guard.
- Open the doc with readonly:false when editing is intended.
- Re-open writable once a transient readonly condition clears.
When it happens
Trigger: Calling store.addBlock (or any mutating API routed through addBlock) on a doc/collection opened with readonly:true, or after the doc was switched to readonly at runtime.
Common situations: Embedding a doc in read-only viewer mode and accidentally firing an edit command; shared/preview links opened readonly but the UI still binds edit handlers; a doc auto-switched to readonly after losing connection or on sync errors.
Related errors
AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12).
Data as JSON: /api/errors/b66c8a79b6030392.
Report an issue: GitHub.