---
title: "Blok BlockAPI — id, name, holder, save, dispatch"
description: "Work with a single block: read its id and name, get its holder element, save it, and dispatch tool events."
source: https://blokeditor.com/docs/block-api/
lastmod: 2026-09-07
---

Framework JavaScript

Core BlockAPI

On this page block.save()

# BlockAPI: work with a single block

Interface for working with individual blocks. Returned by blocks.getById(), blocks.getBlockByIndex(), and blocks.insert().

[Edit this page on GitHub](https://github.com/JackUait/blok/blob/main/docs/src/components/api/api-data.ts)

### Reaching the editor instance

The methods below run on the editor you created with new Blok(). They are available once editor.isReady resolves.

TypeScript

```
// You already hold the instance returned by the constructor.
const editor = new Blok({ holder: 'editor' });
await editor.isReady;

// Call any API method on it.
editor.caret.setToLastBlock('end');
```

## Methods

### block.save()

Promise<void|SavedData>

Save the block content and return its data.

When to use

Returns just this block's data — handy for inspecting or persisting one block without `editor.save()`'s full walk.

TypeScript

```
const block = editor.blocks.getById('block-123');
const saved = await block.save();
// saved resolves to a SavedData object (or undefined if extraction fails):
// { id: 'block-123', tool: 'paragraph', data: { text: 'Block content' }, time: 1717000000000 }
console.log(saved?.data); // { text: 'Block content' }
```

### block.validate(data)

Promise<boolean>

Validate block data against the tool's validation rules.

When to use

Runs the tool's `validate()` against given data; use it to reject empty or malformed blocks before saving.

TypeScript

```
const block = editor.blocks.getById('block-123');
const isValid = await block.validate({ text: 'Hello' });
if (!isValid) {
  console.log('Block data is invalid');
}
```

### block.call(methodName, param?)

void

Call a custom method on the block's tool.

When to use

Escape hatch to invoke a custom method your tool exposes — for behaviour outside the standard BlockTool API.

TypeScript

```
const block = editor.blocks.getById('block-123');
// Call a custom method defined in the tool
block.call('showNotification', { message: 'Hello' });
```

### block.dispatchChange()

void

Manually trigger the onChange callback for this block.

When to use

Call when you mutate a block outside Blok's knowledge (e.g. an async update) so change events and CRDT sync fire.

TypeScript

```
const block = editor.blocks.getById('block-123');
// Trigger change after invisible modification
block.dispatchChange();
```

### block.getActiveToolboxEntry()

Promise<ToolboxConfigEntry | undefined>

Get the active toolbox entry for this block (e.g., Heading 1 vs Heading 2).

When to use

Resolves which toolbox variant is active (e.g. Heading 1 vs 2) — useful for reflecting state in custom UI.

TypeScript

```
const block = editor.blocks.getById('block-123');
const entry = await block.getActiveToolboxEntry();
if (entry) {
  console.log('Active entry:', entry.title);
}
```

### block.getChildren()

BlockAPI[]

This block's direct children as BlockAPI objects, in order. Every BlockAPI the editor hands out is live, so the children it returns can be walked recursively (`child.getChildren()`) and mutated (`child.setParent(...)`, `child.insertChild(...)`).

TypeScript

```
const block = editor.blocks.getById('toggle-123');
block?.getChildren().forEach((child) => console.log(child.id));
```

### block.setParent(parentId)

void

Reparent this block under `parentId`, or back to the root level with `null`. Routes through core's universal `setBlockParent` chokepoint, so the parent's `contentIds` is updated together with this block's `parentId`.

TypeScript

```
const block = editor.blocks.getById('block-123');
block?.setParent('parent-block-id');

// Back to the root level
block?.setParent(null);
```

### block.insertChild(childData?, position?, toolName?, options?)

BlockAPI

Insert a child block under THIS block atomically — creation and parent assignment land in a single undo entry (it delegates to `blocks.insertInsideParent`). `position` is a `BlockChildPosition`: 'start' | 'end' | { before: childId } | { after: childId }, defaulting to 'end' (appended past the whole subtree). `toolName` picks the child's block tool and defaults to `config.defaultBlock`, so a TYPED child is one operation instead of insert-then-reparent; a tool restricted inside table cells is demoted to the default block when the new child would land inside one. `childData` defaults to `{ text: '' }` for the default block and to `{}` when `toolName` names a different tool. `options` carries the same `{ focus, caret, id, tunes, replace }` vocabulary the framework adapters' rich `insert` spec uses, so a container tool never has to hand-roll caret placement or follow up with an `update` to apply tunes.

Parameters

| Parameter | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `childData` | `BlockToolData` | — | `{ text: '' } / {}` | Data for the new child block. Defaults to an empty paragraph blob for the default block tool, and to {} — letting the tool apply its own defaults — when toolName is given. |
| `position` | `BlockChildPosition` | — | `'end'` | Where among the existing children to insert: 'start', 'end', { before: childId } or { after: childId }. |
| `toolName` | `string` | — | `config.defaultBlock` | Block tool to create for the child. Demoted to the default block when it is restricted inside table cells and the new child would land inside one. |
| `options` | `InsertChildOptions` | — | `{}` | focus — make the new child the current block. caret — place the caret inside it ({ position?, offset? }), applied only when a child is actually created. id — explicit id; an id that already exists is insert-if-absent (that child is returned, nothing is created). tunes — block tune data applied at creation. replace — overwrite the child named by an object position instead of inserting beside it; with 'start'/'end' there is nothing to overwrite and the call throws. |

TypeScript

```
const block = editor.blocks.getById('toggle-123');
const child = block?.insertChild({ text: 'Hidden content' });

// Place it among the existing children instead of appending
block?.insertChild({ text: 'First' }, 'start');
block?.insertChild({ text: 'After that one' }, { after: 'child-id' });

// A typed child, in a single undo entry
block?.insertChild({ text: 'Section', level: 3 }, 'end', 'header');

// Drop the caret into the new child at a specific offset
block?.insertChild({ text: 'Draft' }, 'end', undefined, { caret: { offset: 5 } });

// Idempotent: a re-running effect cannot duplicate this child
block?.insertChild({ text: 'Intro' }, 'start', undefined, { id: 'intro-row' });

// Child-level "turn into" — overwrite an existing child, keeping it parented
block?.insertChild({ text: 'Now a heading', level: 3 }, { before: 'child-id' }, 'header', { replace: true });
```

### block.moveChild(childId, delta)

void

Move a direct child by `delta` positions among its siblings, clamped to the valid range. A child carrying its own subtree lands past the target sibling's descendants, not inside them. No-op when `delta` is 0, when `childId` is not a direct child, or when the clamped move would not change the position.

TypeScript

```
const block = editor.blocks.getById('toggle-123');
block?.moveChild('child-id', -1); // one position toward the start
block?.moveChild('child-id', 1);  // one position toward the end
```

## Properties

| Property | Type | Description |
| --- | --- | --- |
| `id` | `string` | Unique block identifier |
| `name` | `string` | Tool name (e.g., "paragraph", "header") |
| `config` | `ToolConfig` | Tool config passed on initialization |
| `holder` | `HTMLElement` | Wrapper of Tool's HTML element |
| `isEmpty` | `boolean` | True if block content is empty |
| `selected` | `boolean` | True if the block is part of a BLOCK-level selection (rubber-band drag, Shift+Click, Shift+Arrow, Cmd/Ctrl+A). A drag across the text of several blocks makes a character-level selection instead, which marks no block as selected — read that one from the document's own Selection. |
| `focusable` | `boolean` | True if block has inputs to be focused |
| `stretched` | `boolean` | Getter/setter for block stretch state |
| `parentId` | `string | null` | Id of the parent block, or null if this block has no parent |
| `contentIds` | `readonly string[]` | Ids of this block's direct children, in order — a read-only copy, so mutating it changes nothing. The block-level counterpart of parentId: it lets a container tool read its children without reaching for the editor API |
| `preservedData` | `BlockToolData` | Last successfully extracted block tool data, synchronous — useful when async save() is not feasible, e.g. clipboard operations |
| `preservedTunes` | `{ [name: string]: BlockTuneData }` | Last successfully extracted block tune data, synchronous — useful when async save() is not feasible, e.g. clipboard operations |
