BlockAPI: work with a single block
Interface for working with individual blocks. Returned by blocks.getById(), blocks.getBlockByIndex(), and blocks.insert().
Reaching the editor instance
The methods below run on the editor you created with new Blok(). They are available once editor.isReady resolves.
// 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.
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.
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?)
voidCall 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.
const block = editor.blocks.getById('block-123');
// Call a custom method defined in the tool
block.call('showNotification', { message: 'Hello' });block.dispatchChange()
voidManually 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.
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.
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(...)`).
const block = editor.blocks.getById('toggle-123');
block?.getChildren().forEach((child) => console.log(child.id));block.setParent(parentId)
voidReparent 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`.
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?)
BlockAPIInsert 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. |
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)
voidMove 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.
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 endProperties
| 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 |