---
title: The editor instance
description: The runtime Edytor object behind every view, its members, the Block, Text and InlineBlock handles, and how to read state reactively.
icon: box
---

Each `<Edytor>` view creates one runtime object, the editor instance. It holds the document the view renders, the selection, the plugins and the command dispatcher. You need it to read or change content from code, or to build UI around the editor (toolbars, menus, status bars).

## Getting the instance

From the parent, bind it:

```svelte title="Editor.svelte" check
<script lang="ts">
	import { Edytor, type EdytorInstance } from 'edytor';

	let edytor = $state<EdytorInstance>();
</script>

<Edytor bind:edytor />
<button onclick={() => edytor?.historyUndo()}>Undo</button>
```

From a component rendered inside the editor (a block snippet, an inline block, a plugin's UI), call `useEdytor()`. It reads the instance from Svelte context and throws `No Edytor found` outside an editor:

```svelte title="BlockToolbar.svelte" check
<script lang="ts">
	import { useEdytor } from 'edytor';

	const edytor = useEdytor();
</script>
```

The instance exists as soon as the component is created, but `edytor.root` stays `undefined` until the document is ready (`edytor.synced` is then `true`). With a `sync` provider that can take a moment.

## Members

| Member | Type | What it is |
| --- | --- | --- |
| `value` | `JSONBlock` | The whole document as JSON, `{ type: 'root', children }`. Reactive. |
| `root` | `Block \| undefined` | The handle of the root block. `root.children` are the top-level blocks. |
| `synced` | `boolean` | The view is bound to a ready document. |
| `document` | `EdytorDocument` | The document this view renders: shared by every view of it, and usable without a view. |
| `facade` | `EdytorDoc` | The document operations, as this view sees them (see below). |
| `selection` | `EdytorSelection` | The selection value and its setters. See [Selection](/docs/editor/selection). |
| `transact(fn)` | `<T>(fn: () => T) => T` | Run several changes as one transaction and one update. A throw from `fn` does not undo the changes made before it; normalization still runs on them, and `fn`'s error is the one thrown (a normalizer that fails then is logged). |
| `dispatcher` | `Dispatcher` | Runs every command. `dispatcher.last` is the last command's result; a command of several steps (a <kbd>Tab</kbd> over several sibling groups, Turn into over several blocks) reads `applied` when any step applied. `dispatcher.lead(plan, body)`, `dispatcher.dispatch(operation, payload, context, body, prepare?)` and `dispatcher.caret(text, offset, ops?)` let a plugin issue one refusable command of its own. See [Triggers: one plan](/docs/plugins/operations#triggers-one-plan). |
| `idToBlock` | `Handles` | Handle lookup: `get(id)` (live blocks only), `block(id)`, `has(id)`. |
| `canMoveBlocks(req)`, `moveBlocks(req)` | | Relative block moves. See [Commands](/docs/editor/commands#moving-blocks). |
| `historyUndo()`, `historyRedo()` | `() => void` | Undo and redo, restoring this view's selection. Each call sets `dispatcher.last`: `applied`, `noop` when the stack is empty, or `refused` in a readonly view or a read-only document. See [History](/docs/editor/history). |
| `readonly` | `boolean` | Whether the view refuses edits. Follows the `readonly` prop. |
| `node` | `HTMLElement \| undefined` | The editable root element, once mounted. |
| `overlay` | `Overlay` | The chrome layer beside the root, `overlay.layer` once mounted. `overlay.add(measure)` runs `measure(origin)` once per invalidated frame: it reads layout (`origin` is the layer's rect) and returns its DOM writes, which run after every measure has read. `overlay.mount(component, props, name, zIndex, measure)` mounts a component in a fixed host placed by `measure(host, origin)`; `overlay.invalidate()` asks for a frame. `add` and `mount` answer their teardown. See [Styling](/docs/customization/styling#the-overlay). |
| `blocks`, `marks`, `inlineBlocks` | `Map<string, Definition>` | The registered definitions, by type. |
| `kinds` | `KindRow[]` | The kind catalogue. See [Blocks](/docs/concepts/blocks#kinds-and-presets). |
| `commands`, `runCommand(id)` | | Registered commands (`block.heading2`, …) and an async runner that resolves to `false` when the command is missing or disabled. |
| `defaultChild(parent)` | `(parent: Block) => string` | The type a new child of `parent` takes. |
| `awareness` | `Awareness` | The document's presence channel. See [Collaboration](/docs/collaboration). |
| `cells` | `Cells` | The reactive render state, one cell per visible block (below). |
| `clear()` | `() => boolean` | Replace the whole document with one empty block and put the caret in it, as one undo step. It is a command: a readonly view or a read-only document refuses it (it answers `false`, and `dispatcher.last` reads `refused`), so a viewer never wipes the shared document. Plugin hooks do not see it. |

### `edytor.facade` and `edytor.document`

`edytor.document` is the `EdytorDocument`: it owns the CRDT document, the shared history, awareness and the facade. Several views can render one document; see [Collaboration](/docs/collaboration).

`edytor.facade` is the document's facade seen through this view. It has every document operation (`insertText`, `formatRange`, `insertBlock`, `moveBlocks`, …) and read (`toJSON`, `childrenIds`, `blockTypeOf`, …). It differs from `edytor.document.facade` in one way: while the document has no block, the view shows a local empty paragraph, and `edytor.facade.virtual()` returns its id. An operation you issue on that id through `edytor.facade` creates the block first. The JSON (`edytor.value`) never contains it.

Facade operations are document writes: they skip plugin hooks and normalization. Handle methods go through the dispatcher and do both. [Commands](/docs/editor/commands) compares them.

## Handles

`Block`, `Text` and `InlineBlock` are handles: small objects that name a piece of the document by id and read the current state from the document each time you access a getter.

```ts
const block = edytor.idToBlock.get('page-title'); // undefined if not live
if (block) {
	block.type; // 'heading'
	block.data; // { level: 'h1' }
	block.children; // Block[]
	block.firstText?.stringContent; // 'A calmer place to think'
	block.value; // this block's JSONBlock
}
```

Handles are not reactive. A template that reads `block.type` does not update when the type changes. Read handles in event handlers and commands, and use the reactive sources below in templates.

| Handle | Identity | Useful members |
| --- | --- | --- |
| `Block` | One per block id: the same object for as long as the block exists. | `id`, `type`, `data`, `parent`, `children`, `content`, `index`, `depth`, `firstText`, `lastText`, `value`, `isInTree`, `isEmpty` (no content and no children; an inline atom is content, so a block holding only a mention is not empty), `hasContent`, `movable`, `convertible` (false for a void block, an island and a list container; true for an emptied document's [virtual paragraph](/docs/collaboration/concurrent-editing#an-emptied-document-shows-a-virtual-paragraph), which a Turn into creates with its kind; a divider, from Turn into or `insertDividerAtSelection`, is created with a paragraph after it), `isContainer` (it renders only its children, like a list), `isListItem`, `list` (the list an item shows in, also after it was outdented out of a nested list into the item holding it), `node`, `textAtOffset(offset)`, `setData(data)` and the block commands (`insertBlockAfter`, `splitBlock`, `setBlock`, `removeBlock`, `nestBlock`, `unNestBlock`, …). `setData(data)` and assigning `type` run `setBlock`. `nestBlock`, `unNestBlock`, `moveBlock` and `moveBlocks` open a closed toggle the block lands in or that adopts blocks, as the keys do. |
| `Text` | A text segment of a block: the `ordinal`-th segment between inline blocks. It has no identity beyond that position. | `parent`, `ordinal`, `stringContent`, `length`, `value` (runs), `isInDocument`, `node`, and the text commands (`insertText`, `deleteText`, `markText`, `removeMarksFromText`, `setText`). |
| `InlineBlock` | One per inline block id. | `id`, `type`, `data`, `parent`, `value`, `selected`, `isInDocument`, `setData(data)` (its block's `setInlineData` command). |

Because a `Text` is a position, don't keep one across edits to find "the same text" later. Keep the block id and an offset, or a selection anchor.

A handle can outlive what it names. Check `block.isInTree`, `text.isInDocument` or `atom.isInDocument` before acting on one you stored; `idToBlock.get(id)` returns `undefined` for a block that is gone.

Inside a command, handle getters see the command's own earlier writes, so a normalizer or a plugin can read the state its operation produced.

## Reading state reactively

Use these in templates, `$derived` and `$effect`:

- **Block snippets** receive a view object, `block`, whose `type`, `data`, `selected` and `focused` are reactive. `block.handle` is the non-reactive handle for commands.

  ```svelte
  {#snippet callout({ block, content })}
  	<span>{block.data.icon}</span>
  	{@render content()}
  	{#if block.selected}<small>selected</small>{/if}
  {/snippet}
  ```

- **`edytor.cells`** is the render state: `cells.rootIds` (the top-level ids) and `cells.get(id)`, a frozen `{ id, type, data, childIds, runs }` that is replaced whenever the block changes. Both are reactive:

  ```svelte
  <script lang="ts">
  	const outline = $derived(
  		(edytor?.cells?.rootIds ?? [])
  			.map((id) => edytor!.cells!.get(id))
  			.filter((cell) => cell?.type === 'heading')
  	);
  </script>
  ```

- **`edytor.value`** is reactive and recomputes the whole JSON after each change. Fine for a word count or a save button; use `cells` for anything per block.

- **`edytor.selection.value`**, `selection.state` and `selection.projection` are reactive, as are `selection.selectedBlocks` and `selection.focusedBlocks`.

Everything else, including handles, `facade` reads and definitions, is imperative: read it when you need it.
