Skip to content
FrameworkJavaScript

Blocks API: insert, move, delete, and render blocks

Manage blocks in the editor — create, delete, update, and reorder content.

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

blocks.clear()

Promise<void>

Remove all blocks from the editor.

When to use

Same as the top-level clear(), exposed on the blocks module. Use when you already hold editor.blocks.

TypeScript
await editor.blocks.clear();
// All content removed; editor keeps one empty paragraph

blocks.render(data)

Promise<void>

Render passed JSON data as blocks, replacing the current document. Echo-safe: when the incoming document is structurally equal to the current saved content (`time`/`version` ignored), the call is a caret-preserving no-op — the `data → render → onSave → setState → data` round-trip needs no consumer-side dedupe. Accepts the loose wire shape (`LooseOutputData`); the editor deep-clones the data, so the passed object is never mutated or retained — frozen store state (Redux, Immer) can be passed directly.

When to use

Replaces the whole document. Echoing the editor's own output back is a caret-preserving no-op — no consumer-side dedupe needed. To add blocks without clearing, use insert() or insertMany().

TypeScript
const data = {
  blocks: [
    { id: '1', type: 'paragraph', data: { text: 'Hello World' } },
    { id: '2', type: 'header', data: { text: 'Title', level: 1 } }
  ]
};
await editor.blocks.render(data);

blocks.renderFromHTML(data)

Promise<void>

Render HTML string as blocks by converting it to block format.

When to use

Handy for importing legacy HTML, but the result is only as good as the tools' paste handlers — JSON via render() preserves more fidelity when you control the source.

TypeScript
const html = '<h1>Title</h1><p>Hello World</p>';
await editor.blocks.renderFromHTML(html);
// HTML is converted to appropriate blocks

blocks.delete(index?, setCaret?)

Promise<void>

Remove the block at the specified index, or current block if no index provided.

When to use

Without an index it deletes the focused block. Deleting the last remaining block automatically inserts a fresh empty paragraph, so the editor is never left without a caret target.

Parameters

ParameterTypeRequiredDefaultDescription
indexnumbercurrent block indexIndex of the block to delete.
setCaretbooleantrueWhether to move the caret to the surviving block after deletion; pass false to avoid stealing the user's caret during programmatic deletion.
TypeScript
// Delete current block
await editor.blocks.delete();

// Delete block at index 0
await editor.blocks.delete(0);

// Delete without moving the user's caret (programmatic deletion)
await editor.blocks.delete(0, false);

blocks.move(toIndex, fromIndex?)

void

Moves a block to a new position. If fromIndex is not provided, moves the current block.

When to use

toIndex is in post-removal index space: for a forward move, subtract 1 or the block lands one slot too far.

TypeScript
// Move current block to top
editor.blocks.move(0);

// Move block from index 2 to index 0
editor.blocks.move(0, 2);

blocks.getBlockByIndex(index)

BlockAPI | undefined

Get the BlockAPI object for the block at the specified index.

When to use

Returns undefined for out-of-range indexes — calling a method on an undefined result throws, so check the return value first.

TypeScript
const block = editor.blocks.getBlockByIndex(0);
if (block) {
  console.log(block.id, block.name);
}

blocks.getById(id)

BlockAPI | null

Get the BlockAPI object for the block with the specified ID.

When to use

The stable way to reference a block across edits, since indexes shift. Returns null if the id no longer exists.

TypeScript
const block = editor.blocks.getById('block-123');
if (block) {
  await editor.blocks.update(block.id, { text: 'New content' });
}

blocks.getCurrentBlockIndex()

number

Get the index of the currently focused block.

When to use

Reflects the block holding the caret. Returns -1 when nothing is focused, so check before using it as an index.

TypeScript
const index = editor.blocks.getCurrentBlockIndex();
console.log('Current block index:', index);

blocks.getBlockIndex(blockId)

number | undefined

Get the index of a block by its ID.

When to use

Finds where a known block sits right now; pair with getById() when you have an id but need a position.

TypeScript
const index = editor.blocks.getBlockIndex('block-123');
if (index !== undefined) {
  console.log('Block is at index:', index);
}

blocks.getBlockByElement(element)

BlockAPI | undefined

Get the BlockAPI object for the block containing the given HTML element.

When to use

Bridges from a raw DOM node (e.g. a click target) back to its block — useful in custom event handlers.

TypeScript
document.addEventListener('click', (e) => {
  const block = editor.blocks.getBlockByElement(e.target);
  if (block) {
    console.log('Clicked on block:', block.id);
  }
});

blocks.getChildren(parentId)

BlockAPI[]

Get all child blocks of a parent container block.

When to use

Returns the direct children of a container block (a column, toggle, or database). Empty array if it has none.

TypeScript
const children = editor.blocks.getChildren('parent-block-id');
children.forEach(child => {
  console.log('Child:', child.id);
});

blocks.getBlocksCount()

number

Get the total number of blocks in the editor.

When to use

Counts every block including nested children (columns, toggles, table cells), not just top-level blocks. Use it for bounds before an index-based insert() or move().

TypeScript
const count = editor.blocks.getBlocksCount();
console.log('Total blocks:', count);

blocks.insert(type?, data?, config?, index?, needToFocus?, replace?, id?, tunes?)

BlockAPI

Insert a new block with full control over its properties and position.

When to use

Every parameter is optional — call insert() for an empty paragraph, or pass type/data/index for full control. Pass replace: true to overwrite the block at index.

Parameters

ParameterTypeRequiredDefaultDescription
typestringconfig.defaultBlockTool name to instantiate.
dataBlockToolData{}Initial tool data for the new block.
configToolConfig{}Tool config for this block instance.
indexnumbercurrent block index + 1Position to insert the block at.
needToFocusbooleantrueWhether to move focus to the inserted block.
replacebooleanfalseReplace the existing block at index instead of inserting around it.
idstringauto-generatedCustom id for the new block.
tunes{ [name: string]: BlockTuneData }undefinedOptional tune data applied at creation, keyed by tune name.

Errors

  • No type is given and no defaultBlock is configured.

    Could not insert Block. Tool name is not specified.

    Pass an explicit type, or set defaultBlock in the editor config.

  • The resolved tool name is not registered in the editor.

    Could not compose Block. Tool «<type>» not found.

    Register the tool in the editor's tools config before inserting a block of that type.

  • replace: true is passed but no block exists at index.

    Could not replace Block at index <index>. Block not found.

    Check index against blocks.getBlocksCount() before calling with replace: true.

TypeScript
// Insert after the current block with the default type
const block = editor.blocks.insert();
// → BlockAPI { id: 'kP3xQ...', name: 'paragraph', ... }

// Insert paragraph with data at index 0
const block = editor.blocks.insert('paragraph', { text: 'Hello' }, undefined, 0);

// Insert with custom ID
const block = editor.blocks.insert('header', { text: 'Title' }, undefined, undefined, undefined, undefined, 'custom-id');

blocks.insertMany(blocks, index?)

BlockAPI[]

Insert multiple blocks at once. When the index is omitted, the blocks are appended at the end of the document.

When to use

Bulk-insert in one operation (and one undo step) — far cheaper than looping insert() for large pastes or imports.

Parameters

ParameterTypeRequiredDefaultDescription
blocksOutputBlockData[] | LooseOutputBlockData[]RequiredThe blocks to insert. Loose wire blocks are accepted — a null data becomes {}, a null/empty id gets a generated one.
indexnumberend of documentPosition to insert at. When omitted, defaults to appending at the end of the document.

Errors

  • The provided index is negative.

    Index should be greater than or equal to 0

    Pass an index of 0 or greater, or omit it to append.

TypeScript
const blocksToInsert = [
  { id: '1', type: 'paragraph', data: { text: 'First' } },
  { id: '2', type: 'paragraph', data: { text: 'Second' } }
];
const inserted = editor.blocks.insertMany(blocksToInsert, 0);
console.log('Inserted:', inserted.length, 'blocks');

blocks.composeBlockData(toolName)

Promise<BlockToolData>

Create empty block data for the specified tool type.

When to use

Builds a tool's default data without inserting anything — useful when you need a valid empty payload for insert() or update().

Errors

  • toolName is not a registered tool.

    Block Tool with type "<toolName>" not found

    Register the tool in the editor's tools config first.

TypeScript
const emptyData = await editor.blocks.composeBlockData('paragraph');
// Returns: { text: '' } or appropriate empty state for the tool

blocks.update(id, data?, tunes?)

Promise<BlockAPI>

Update a block's data and/or tunes.

When to use

Patches data/tunes in place, keeping the block id and type. To change the type, use convert() instead.

Parameters

ParameterTypeRequiredDefaultDescription
idstringRequiredId of the block to update.
dataPartial<BlockToolData>undefinedPartial data merged into the block's existing data.
tunesRecord<string, BlockTuneData>undefinedTune data merged into the block's existing tunes.

Errors

  • No block exists with the given id.

    Block with id "<id>" not found

    Confirm the id with blocks.getById() before calling update().

TypeScript
// Update block data
const block = await editor.blocks.update('block-123', { text: 'New text' });

// Update with tunes
const block = await editor.blocks.update('block-123', undefined, { alignment: 'center' });

// Guard against an id that no longer exists (e.g. the block was deleted
// concurrently) instead of letting the rejection surface unhandled
try {
  await editor.blocks.update(staleId, { text: 'New text' });
} catch {
  console.warn('Block was already removed, skipping update');
}

blocks.convert(id, newType, dataOverrides?)

Promise<BlockAPI>

Convert a block to a different type. Both tools must support conversion config.

When to use

Both the source and target tools must declare conversionConfig, otherwise the call throws. Preserves text where possible.

Parameters

ParameterTypeRequiredDefaultDescription
idstringRequiredId of the block to convert. Its tool must declare conversionConfig.export.
newTypestringRequiredName of the registered tool to convert to. Its tool must declare conversionConfig.import.
dataOverridesBlockToolDataundefinedData fields to overwrite on the resulting block after conversion.

Errors

  • No block exists with the given id.

    Block with id "<id>" not found

    Confirm the id with blocks.getById() before calling convert().

  • newType is not a registered tool.

    Block Tool with type "<newType>" not found

    Register the target tool in the editor's tools config.

  • The source tool has no conversionConfig.export, the target tool has no conversionConfig.import, or neither does.

    Conversion from "<sourceType>" to "<newType>" is not possible. <ToolName(s)> tool(s) should provide a "conversionConfig"

    Add a conversionConfig to whichever tool is missing one, or convert through an intermediate tool that supports both directions.

TypeScript
// Convert paragraph to header
const headerBlock = await editor.blocks.convert('block-123', 'header', { level: 2 });

// Convert with data overrides
const headerBlock = await editor.blocks.convert('block-123', 'header', { text: 'New Title', level: 1 });

// Not every pair of tools supports conversion — guard it rather than
// assuming the target type is always convertible
try {
  await editor.blocks.convert('block-123', 'table');
} catch (error) {
  console.warn('Conversion not supported between these tools:', error);
}

blocks.splitBlock(currentBlockId, currentBlockData, newBlockType, newBlockData, insertIndex)

BlockAPI

Atomically split a block by updating the current block and inserting a new block. Both operations are grouped into a single undo entry.

When to use

Use for caret-aware splits (e.g. Enter mid-text); the update + insert land as a single undo entry.

TypeScript
// Split a paragraph at cursor position
const newBlock = editor.blocks.splitBlock(
  'current-block-id',
  { text: 'First part' },
  'paragraph',
  { text: 'Second part' },
  1
);

blocks.stopBlockMutationWatching(index)

void

Stop mutation watching on a block at the specified index. Use this to prevent spurious block-changed events during block replacement operations.

When to use

Niche: call before programmatically replacing a block's DOM to suppress spurious block-changed events; the next render re-arms it.

TypeScript
// Replace a block without triggering change events
editor.blocks.stopBlockMutationWatching(0);
// Perform block replacement...
// Mutation observer will not fire for this block