toeverything/AFFiNE · error · BlockSuiteError
ErrorCode.SelectionError
ErrorCode.SelectionError
Error message
You must override this method
What it means
BaseSelection.fromJSON() is the base stub that every concrete selection class must override, because each selection type needs its own deserialization logic. If a registered selection subclass ships without a static fromJSON override, the first deserialization (undo/redo restoring selection state, remote selection updates, persisted selection) dispatches to the stub and throws SelectionError 'You must override this method'.
Solutions
- Implement static fromJSON(json) on the selection subclass: parse the fields, return new MySelection({ blockId, ... }).
- Mirror the fields you serialize in equals/onChange so round-trips are lossless.
- Register the class via SelectionExtension(MySelection) so deserialization uses your constructor.
- If you never persist/remote-sync this selection type, reconsider whether a custom class is needed at all.
Example fix
// before
class MySelection extends BaseSelection {
static override type = 'my';
// no fromJSON -> throws when deserialized
}
// after
class MySelection extends BaseSelection {
static override type = 'my-selection';
static override fromJSON(json: Record<string, unknown>) {
return new MySelection({ blockId: json.blockId as string });
}
constructor(opts: BaseSelectionOptions) { super({ blockId: opts.blockId }); }
override equals(other: BaseSelection) { return other instanceof MySelection && other.blockId === this.blockId; }
} Defensive patterns
Strategy: type-guard
Type guard
import { BaseSelection } from '@blocksuite/store';
function implementsFromJSON(
ctor: typeof BaseSelection
): boolean {
return ctor.fromJSON !== BaseSelection.fromJSON;
}
// guard before registration:
if (!implementsFromJSON(MySelection)) throw new Error('MySelection must implement static fromJSON'); Prevention
- Implement static fromJSON on every selection subclass before registering it.
- Add a startup assertion that each registered selection overrides fromJSON.
- Cover serialization round-trips (set → undo → restore) in tests for custom selection types.
When it happens
Trigger: Defining a custom selection class extending BaseSelection without implementing static fromJSON(json), then triggering selection (de)serialization: undoManager pop restoring 'selection-state' from stack item meta, or selectionManager.set / fromJSON on remote updates.
Common situations: Writing custom block selection types for new flavours; copying a selection class skeleton from docs/examples that omits fromJSON; minor-version upgrades where fromJSON became required/typed differently.
Understand the failure class
Background: "NotImplementedError: Subclasses should override this method" / "must be implemented" — abstract method errors explained — this error's family across 40 libraries.
Related errors
- ErrorCode.InlineEditorError
- ErrorCode.SelectionError
- 1
- a should be smaller than b
- Block cannot have parent
AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18).
Data as JSON: /api/errors/3a9fe509b1f82162.
Report an issue: GitHub.
Appendix: source
Thrown at blocksuite/framework/store/src/extension/selection/base.ts:31
static readonly recoverable: boolean = false;
readonly blockId: string;
get group(): string {
return (this.constructor as SelectionConstructor).group;
}
get type(): string {
return (this.constructor as SelectionConstructor).type as string;
}
constructor({ blockId }: BaseSelectionOptions) {
this.blockId = blockId;
}
static fromJSON(_: Record<string, unknown>): BaseSelection {
throw new BlockSuiteError(
ErrorCode.SelectionError,
'You must override this method'
);
}
abstract equals(other: BaseSelection): boolean;
is<T extends SelectionConstructor>(
type: T
): this is T extends SelectionConstructor<infer U> ? U : never {
return this.type === type.type;
}
abstract toJSON(): Record<string, unknown>;
}
View on GitHub (pinned to b4c8548c09)