Scrivr
API Reference

Editor API

Complete reference for the Editor class — methods, properties, and lifecycle.

The Editor class is the central orchestrator of every Scrivr instance. It owns the document state, layout engine, and extension system. Consumers interact with it directly (headless) or via the useScrivrEditor React hook.

import { Editor, StarterKit } from '@scrivr/core';

const editor = new Editor({
  extensions: [StarterKit],
  onChange: (state) => console.log('changed', state),
});

editor.mount(containerElement);

Constructor

new Editor(options: EditorOptions)

Accepts an EditorOptions object. Builds the ProseMirror schema from extensions, runs the initial layout, and starts the cursor blink timer.


Properties

schema

get schema(): Schema

The merged ProseMirror schema built from all active extensions. Use this instead of importing a schema directly — it reflects exactly which node and mark types are loaded.

pageConfig

readonly pageConfig: PageConfig

The active page dimensions. Set once at construction from EditorOptions.pageConfig. See PageConfig.

charMap

get charMap(): CharacterMap

The spatial index mapping doc positions to (x, y, page) canvas coordinates. Owned by the layout engine and updated after every layout pass.

cursorManager

readonly cursorManager: CursorManager

Manages the cursor blink timer. Read cursorManager.isVisible to know whether to draw or clear the cursor in a custom overlay renderer.

toolbarItems

readonly toolbarItems: ToolbarItemSpec[]

Toolbar item specs contributed by all extensions, in registration order. Data-only — no framework dependency. Use this to build a data-driven toolbar without hard-coding button lists.

commands

readonly commands: Record<string, (...args: unknown[]) => void>

All bound extension commands. Closures that always read the latest state at call time.

editor.commands.toggleBold();
editor.commands.setHeading2();
editor.commands.undo();

isFocused

get isFocused(): boolean

Whether the editor's hidden textarea currently has focus.

loadingState

get loadingState(): 'syncing' | 'rendering' | 'ready'

Current loading phase:

ValueMeaning
'syncing'Waiting for collaborative sync (only when startReady: false).
'rendering'Layout engine is running for the first time.
'ready'Document is fully laid out and visible.

cursorPage

get cursorPage(): number

The 1-based page number of the current cursor position.


Lifecycle

mount(container)

mount(container: HTMLElement): void

Attaches the keyboard, IME, and paste event listeners to the given container. Called automatically by <Scrivr />.

unmount()

unmount(): void

Detaches event listeners. The editor state is preserved — the editor can be re-mounted. Called automatically by <Scrivr /> on unmount.

destroy()

destroy(): void

Full teardown: runs extension cleanup, stops the cursor blink timer, and calls unmount(). After destroy() the instance must not be used.

focus()

focus(): void

Programmatically focuses the editor.

setReady(ready)

setReady(ready: boolean): void

Unblocks layout flushing after a collaborative sync. Managed automatically by the Collaboration extension — you do not need to call this manually.


State

getState()

getState(): EditorState

Returns the current ProseMirror EditorState. States are immutable — reference equality detects changes.

getSnapshot()

getSnapshot(): EditorState

Returns the current state. Used as the getSnapshot argument for React's useSyncExternalStore.

isActive(name, attrs?)

isActive(name: string, attrs?: Record<string, unknown>): boolean

Returns true if the named mark or block type is active at the current cursor or selection.

editor.isActive('bold');
editor.isActive('heading', { level: 1 });

getSelectionSnapshot()

getSelectionSnapshot(): SelectionSnapshot

Returns a lightweight snapshot with everything a toolbar or floating menu needs — active marks, block type, cursor position — without requiring ProseMirror imports. See SelectionSnapshot.

getActiveMarks()

getActiveMarks(): string[]

Names of marks active at the cursor. For a range selection, only marks present on every character in the range are returned.

getActiveMarkAttrs()

getActiveMarkAttrs(): Record<string, Record<string, unknown>>

Attributes of each active mark, keyed by mark name.

const attrs = editor.getActiveMarkAttrs();
attrs.color?.color;    // '#dc2626'
attrs.font_size?.size; // 18

getBlockInfo()

getBlockInfo(): { blockType: string; blockAttrs: Record<string, unknown> }

Type name and attributes of the block node containing the cursor.

getMarkdown()

getMarkdown(): string

Serializes the full document to Markdown using extension-contributed serializer rules.

getMarkdownSerializer()

getMarkdownSerializer(): MarkdownSerializer

Returns a configured MarkdownSerializer built from all extension rules. Used internally by getMarkdown() and exportToMarkdown() from @scrivr/export.


Layout

layout

get layout(): DocumentLayout

The current DocumentLayout. Calls ensureLayout() internally so the result always reflects the latest state.

ensureLayout()

ensureLayout(): void

Forces the layout engine to run synchronously if the current layout is stale. Normally triggered implicitly by editor.layout and movement methods.

ensurePagePopulated(pageNumber)

ensurePagePopulated(pageNumber: number): void

Ensures the given page (1-based) has been laid out and its character positions are indexed.


Viewport & Position

getViewportRect(from, to)

getViewportRect(from: number, to: number): DOMRect | null

Converts a doc position range to a viewport-relative DOMRect. Returns null if the positions are not in the character map or no rendering adapter is mounted.

const rect = editor.getViewportRect(snap.from, snap.to);
if (rect) {
  menuEl.style.top  = `${rect.bottom + 8}px`;
  menuEl.style.left = `${rect.left}px`;
}

getNodeViewportRect(docPos)

getNodeViewportRect(docPos: number): DOMRect | null

Converts a single node's doc position to a viewport-relative DOMRect covering the full visual bounds of that node. Used by createImageMenu to position the image toolbar below the selected image.

scrollCursorIntoView()

scrollCursorIntoView(): void

Scrolls the viewport so the current cursor position is visible.


Cursor & Selection

moveCursorTo(pos)

moveCursorTo(pos: number): void

Collapses the cursor to a given doc position.

setSelection(anchor, head)

setSelection(anchor: number, head: number): void

Sets an explicit selection range. Both values are clamped to valid doc bounds.

selectNode(docPos)

selectNode(docPos: number): void

Creates a NodeSelection at the given doc position. Falls back to moveCursorTo if the node is not selectable. Use this to programmatically select images and other inline objects.

setNodeAttrs(docPos, attrs)

setNodeAttrs(docPos: number, attrs: Record<string, unknown>): void

Merges attributes into the node at docPos. Use this to update image dimensions, alignment, or any other node-level attribute without writing a custom command.

moveLeft / moveRight / moveUp / moveDown

moveLeft(extend?: boolean): void
moveRight(extend?: boolean): void
moveUp(extend?: boolean): void
moveDown(extend?: boolean): void

Arrow-key cursor movement. Pass extend: true to grow the selection (Shift+arrow).


Subscription

subscribe(listener)

subscribe(listener: () => void): () => void

Registers a listener called on every editor notification: state changes, focus changes, and cursor blink ticks. Returns an unsubscribe function.

const unsubscribe = editor.subscribe(() => forceUpdate());

redraw()

redraw(): void

Triggers a repaint without a document or selection change. Use when external state (e.g. collaborative awareness data) changes and the overlay needs refreshing.


Extension Points

addOverlayRenderHandler(handler)

addOverlayRenderHandler(handler: OverlayRenderHandler): () => void

Registers a canvas draw function on the overlay layer. Called after the built-in cursor and selection are painted. Returns an unregister function.

const unregister = editor.addOverlayRenderHandler(
  (ctx, pageNumber, pageConfig, charMap) => {
    // draw custom overlay for this page
  }
);

setPageElementLookup(fn)

setPageElementLookup(fn: ((page: number) => HTMLElement | null) | null): void

Registers the function used to resolve a 1-based page number to its DOM element. Called automatically by <Scrivr />. Only needed when building a custom rendering adapter.

_applyTransaction(tr)

_applyTransaction(tr: Transaction): void

Applies a ProseMirror transaction directly, bypassing input positioning. Used by extensions and the AI toolkit to dispatch transactions that originate outside normal user input (e.g. Yjs remote sync, ghost text updates).

Prefixed with _ to signal it is infrastructure-facing. Prefer editor.commands.* for document mutations from application code.

On this page