Skip to content
FrameworkJavaScript

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.

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

ParameterTypeRequiredDefaultDescription
childDataBlockToolData{ 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.
positionBlockChildPosition'end'Where among the existing children to insert: 'start', 'end', { before: childId } or { after: childId }.
toolNamestringconfig.defaultBlockBlock 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.
optionsInsertChildOptions{}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

PropertyTypeDescription
idstringUnique block identifier
namestringTool name (e.g., "paragraph", "header")
configToolConfigTool config passed on initialization
holderHTMLElementWrapper of Tool's HTML element
isEmptybooleanTrue if block content is empty
selectedbooleanTrue 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.
focusablebooleanTrue if block has inputs to be focused
stretchedbooleanGetter/setter for block stretch state
parentIdstring | nullId of the parent block, or null if this block has no parent
contentIdsreadonly 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
preservedDataBlockToolDataLast 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