Skip to content
ФреймворкJavaScript

Blocks API: вставка, перенос и удаление блоков

Управление блоками в редакторе — создание, удаление, обновление и переупорядочивание контента.

Как получить экземпляр редактора

Методы ниже вызываются на редакторе, созданном через new Blok(). Они доступны после того, как разрешится editor.isReady.

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');

Методы

blocks.clear()

Promise<void>

Remove all blocks from the editor.

Когда использовать

То же, что clear() верхнего уровня, но на модуле blocks. Используйте, когда уже есть ссылка на 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.

Когда использовать

Заменяет весь документ. Повторная передача собственного вывода редактора — no-op с сохранением каретки, дедупликация на стороне потребителя не нужна. Чтобы добавить блоки без очистки, используйте insert() или 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.

Когда использовать

Удобно для импорта старого HTML, но результат зависит от обработчиков вставки инструментов — JSON через render() сохраняет больше точности, когда источник под контролем.

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

blocks.importMarkdown(md, options?)

Promise<OutputData>

Convert a Markdown string to blocks and render them, REPLACING the current document — it calls `blocks.render()` internally. The converter is lazy-loaded on first call, and the resolved OutputData is the document that was rendered. `options` is a `MarkdownImportConfig` (tool mapping, GFM toggle, micromark/mdast extensions). For additive insertion, use `markdownToBlocks()` from the standalone `@bloklabs/core/markdown` subpath together with `blocks.insertMany()`.

Параметры

ПараметрТипОбязательныйПо умолчаниюОписание
mdstringОбязательныйMarkdown source string.
optionsMarkdownImportConfigundefinedTool mapping, GFM toggle, and micromark/mdast extensions.
TypeScript
const data = await editor.blocks.importMarkdown('# Title\n\n- one\n- two');
// The whole document is replaced; data is the rendered OutputData

blocks.exportMarkdown()

Promise<string>

Serialize the current document to Markdown — the outbound twin of `importMarkdown`. Blocks are read through the Saver, so the output reflects the saved (validated) document rather than raw DOM, and the promise resolves to '' when there is nothing to save. Blocks owned by a table cell are serialized inside the pipe table instead of being repeated as loose lines. What Markdown cannot express is degraded: table `colspan`/`rowspan` and heading columns are dropped, and a table with no heading row gets an empty header row, since GFM requires one.

TypeScript
const md = await editor.blocks.exportMarkdown();
// → '# Title\n\n- one\n- two'

blocks.delete(index?, setCaret?)

Promise<void>

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

Когда использовать

Без индекса удаляет блок в фокусе. Удаление последнего оставшегося блока автоматически вставляет свежий пустой параграф, поэтому редактор никогда не остаётся без цели для курсора.

Параметры

ПараметрТипОбязательныйПо умолчаниюОписание
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.

Когда использовать

toIndex — это индекс после удаления: при перемещении вперёд вычтите 1, иначе блок встанет на позицию дальше нужной.

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.

Когда использовать

Возвращает undefined для индексов вне диапазона — вызов метода на undefined приведёт к ошибке, поэтому сначала проверьте результат.

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.

Когда использовать

Надёжный способ ссылаться на блок между правками, так как индексы смещаются. Возвращает null, если id больше не существует.

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.

Когда использовать

Отражает блок с курсором. Возвращает -1, когда ничего не в фокусе, — проверяйте перед использованием как индекс.

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.

Когда использовать

Узнаёт текущую позицию известного блока; сочетайте с getById(), когда есть id, а нужна позиция.

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.

Когда использовать

Мост от DOM-узла (например, цели клика) обратно к его блоку — полезно в собственных обработчиках.

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

blocks.scrollToBlock(id)

void

Scroll a block into view, select it, pulse the arrival highlight and announce the navigation to assistive tech — the public counterpart of the boot-time URL-hash scroll. No-op when no block with that id is in the document. Framework adapters that mount into a detached holder (React/Vue/Angular) render seeded content before it joins the page, so the boot hash scroll defers; @bloklabs/react drains it automatically once the holder connects — call this yourself for deep-linking after the editor is ready.

TypeScript
editor.blocks.scrollToBlock(nodeId);

blocks.getChildren(parentId)

BlockAPI[]

Get all child blocks of a parent container block.

Когда использовать

Возвращает прямых потомков блока-контейнера (колонки, тоггла или базы данных). Пустой массив, если потомков нет.

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

blocks.setBlockParent(blockId, parentId)

void

Reparent a block: updates the block's `parentId` and the parent's `contentIds` through core's single reparent chokepoint. Pass `null` to move the block back to the root level. An unknown `blockId` is a no-op that logs a warning; a `parentId` that would make the block a descendant of itself throws.

TypeScript
// Move a block into a container block
editor.blocks.setBlockParent('child-block-id', 'parent-block-id');

// Move it back to the root level
editor.blocks.setBlockParent('child-block-id', null);

blocks.insertInsideParent(parentId, insertIndex, childData?, toolName?)

BlockAPI

Insert a block as a child of `parentId` atomically: the creation and the parent assignment are grouped into ONE undo entry, so a single Cmd+Z removes it completely (preferable to `insert()` followed by a reparent, which is two). `insertIndex` is the FLAT document index where the child should appear. `toolName` picks the child's block tool and defaults to `config.defaultBlock`; a tool that is restricted inside table cells is demoted to the default block when the new child would land inside one, and an unregistered name throws before anything is written. `childData` defaults to `{ text: '' }` for the default block, and to `{}` — letting the tool apply its own defaults — whenever `toolName` names a different tool.

TypeScript
const parentIndex = editor.blocks.getBlockIndex('parent-block-id') ?? 0;
const child = editor.blocks.insertInsideParent(
  'parent-block-id',
  parentIndex + 1,
  { text: 'Nested content' },
);

// A TYPED child in the same single undo entry — no insert-then-reparent
editor.blocks.insertInsideParent(
  'parent-block-id',
  parentIndex + 1,
  { text: 'Nested heading', level: 2 },
  'header',
);

blocks.getBlocksCount()

number

Get the total number of blocks in the editor.

Когда использовать

Считает все блоки, включая вложенные дочерние (колонки, тогглы, ячейки таблиц), а не только блоки верхнего уровня. Используйте для границ перед insert() или move() по индексу.

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

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

BlockAPI

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

Когда использовать

Все параметры необязательны — insert() даёт пустой параграф, либо передайте type/data/index для контроля. replace: true заменяет блок по индексу index.

Параметры

ПараметрТипОбязательныйПо умолчаниюОписание
typestringconfig.defaultBlockИмя инструмента для создания блока.
dataBlockToolData{}Начальные данные инструмента для нового блока.
configToolConfig{}Игнорируется — принимается только ради сохранения позиционной сигнатуры. Редактор связывает его с _config и никогда не читает, а опции вставки в block-manager не содержат поля config, поэтому созданный блок использует конфигурацию инструмента уровня редактора из config.tools. Передавайте undefined.
indexnumbercurrent block index + 1Позиция для вставки блока.
needToFocusbooleantrueПереводить ли фокус на вставленный блок. Передайте false, чтобы вставить блок, не перемещая каретку.
replacebooleanfalseЗаменить существующий блок по индексу вместо вставки рядом с ним.
idstringauto-generatedСобственный id для нового блока.
tunes{ [name: string]: BlockTuneData }undefinedНеобязательные данные тюнов, применяемые при создании, с ключами по имени тюна.
originBlockOrigin'api'Почему блок создаётся. Передаётся в конструктор инструмента как origin — именно так контейнерный инструмент отличает настоящее создание (можно засеять детей по умолчанию) от повторной материализации: загрузки документа, воспроизведения undo/redo или вставки (засевать нельзя). Передавайте 'user', когда вставка идёт из вашего собственного интерфейса — своей панели, слэш-меню или горячей клавиши, — чтобы встроенные контейнеры Blok (column_list, column) и ваши собственные вели себя так же, как при вставке из штатного меню. Для программных вставок и перезагрузки данных параметр можно не передавать: значение по умолчанию 'api' тоже считается созданием, но никогда не выдаётся за жест пользователя.

Ошибки

  • Не указан type, и в конфигурации не задан defaultBlock.

    Could not insert Block. Tool name is not specified.

    Передайте явный type или задайте defaultBlock в конфигурации редактора.

  • Определённое имя инструмента не зарегистрировано в редакторе.

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

    Зарегистрируйте инструмент в конфигурации tools редактора перед вставкой блока этого типа.

  • Передан replace: true, но по index нет блока.

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

    Проверьте index через blocks.getBlocksCount() перед вызовом с 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');

// From your own slash menu / toolbar: declare the user gesture so a container
// tool seeds its default children (see BlockOrigin on the tool contract)
const block = editor.blocks.insert('column_list', undefined, undefined, index, undefined, undefined, undefined, undefined, 'user');

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.

Когда использовать

Массовая вставка за одну операцию (и один шаг отмены) — гораздо дешевле цикла из insert() для больших вставок или импорта.

Параметры

ПараметрТипОбязательныйПо умолчаниюОписание
blocksOutputBlockData[] | LooseOutputBlockData[]ОбязательныйThe 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.

Ошибки

  • 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.

Когда использовать

Создаёт данные инструмента по умолчанию, ничего не вставляя — удобно, когда нужен корректный пустой payload для insert() или update().

Ошибки

  • 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 the block's tool implements `setData(newData)` on its prototype — every React/Vue/Angular block does, as do the built-in header, list, code, toggle and table tools — the data update is applied IN PLACE: the same block instance, the same DOM holder, the same mounted component keep living, so ephemeral tool state, adopted child blocks and the caret all survive. That is what makes `update()` safe to call on every keystroke (renaming a card while the user types). Tools without `setData`, a tool whose `setData` returns `false` (it needs a different DOM shape), and any call that passes `tunes` fall back to recomposing the block: a fresh tool instance replaces the old one, which is destroyed.

Когда использовать

Изменяет data/tunes на месте, сохраняя id и тип блока. Чтобы сменить тип, используйте convert().

Параметры

ПараметрТипОбязательныйПо умолчаниюОписание
idstringОбязательныйId блока для обновления.
dataPartial<BlockToolData>undefinedЧастичные данные, объединяемые с существующими данными блока.
tunesRecord<string, BlockTuneData>undefinedДанные тюнов, объединяемые с существующими тюнами блока.

Ошибки

  • Блок с указанным id не существует.

    Block with id "<id>" not found

    Проверьте id через blocks.getById() перед вызовом 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.

Когда использовать

И исходный, и целевой инструменты должны объявлять conversionConfig, иначе вызов выбросит ошибку. По возможности сохраняет текст.

Параметры

ПараметрТипОбязательныйПо умолчаниюОписание
idstringОбязательныйId блока для конвертации. Его инструмент должен объявлять conversionConfig.export.
newTypestringОбязательныйИмя зарегистрированного инструмента для конвертации. Его инструмент должен объявлять conversionConfig.import.
dataOverridesBlockToolDataundefinedПоля данных для перезаписи в результирующем блоке после конвертации.

Ошибки

  • Блок с указанным id не существует.

    Block with id "<id>" not found

    Проверьте id через blocks.getById() перед вызовом convert().

  • newType не является зарегистрированным инструментом.

    Block Tool with type "<newType>" not found

    Зарегистрируйте целевой инструмент в конфигурации tools редактора.

  • У исходного инструмента нет conversionConfig.export, у целевого нет conversionConfig.import, либо нет ни того, ни другого.

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

    Добавьте conversionConfig тому инструменту, у которого его нет, либо конвертируйте через промежуточный инструмент, поддерживающий оба направления.

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.

Когда использовать

Используйте для разбиения с учётом курсора (например, Enter посреди текста); update и insert образуют один шаг отмены.

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.

Когда использовать

Нишевое: вызывайте перед программной заменой DOM блока, чтобы подавить ложные события block-changed; следующий рендер включит наблюдение снова.

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

blocks.startBlockMutationWatching(blockId)

void

Re-arm mutation watching on a block previously silenced by `stopBlockMutationWatching`. It takes an **id**, not an index, because inserts and replacements between the two calls shift indexes; an id that no longer exists is silently skipped — a block replaced in place was constructed with its own watcher.

TypeScript
const blockId = editor.blocks.getBlockByIndex(0)?.id;
editor.blocks.stopBlockMutationWatching(0);
// Perform block replacement...
if (blockId) {
  editor.blocks.startBlockMutationWatching(blockId);
}

blocks.transact(fn)

void

Group every block operation performed inside `fn` into a single undo entry. `fn` must be SYNCHRONOUS — operations that land after an await are no longer part of the group. Use it so structural edits are not partially undoable.

TypeScript
editor.blocks.transact(() => {
  editor.blocks.insert('paragraph', { text: 'One' });
  editor.blocks.insert('paragraph', { text: 'Two' });
});
// A single Cmd+Z removes both blocks

blocks.beginTransaction()

void

Open an undo group that stays open across async boundaries. Every block operation until `endTransaction()` lands in one undo entry. Use it for pointer gestures that mutate the document continuously (dragging a table's corner to add rows), where `transact()` cannot help because it only wraps a synchronous function. Every call must be paired with `endTransaction()`.

TypeScript
editor.blocks.beginTransaction();
// ... continuous mutations across async boundaries ...
editor.blocks.endTransaction();

blocks.endTransaction()

void

Close the undo group opened by `beginTransaction()`.

TypeScript
editor.blocks.beginTransaction();
// ... continuous mutations across async boundaries ...
editor.blocks.endTransaction();

blocks.transactWithoutCapture(fn)

void

Run block operations without recording anything in the undo history. Use it for auto-repair and normalization (e.g. ensuring an empty cell always has a block) that Cmd+Z should never step through.

TypeScript
editor.blocks.transactWithoutCapture(() => {
  editor.blocks.insert('paragraph', {}, undefined, 0);
});
// Nothing was added to the undo history

blocks.setPointerDragActive(active)

void

Tell core that a pointer drag interaction started or ended. While it is active, DOM-mutation-triggered Yjs syncs are suppressed so browser DOM churn during the drag cannot corrupt Yjs state.

TypeScript
editor.blocks.setPointerDragActive(true);
// ... run the drag gesture ...
editor.blocks.setPointerDragActive(false);

Свойства

СвойствоТипОписание
isSyncingFromYjsbooleanReadonly getter — true while a Yjs sync operation (undo/redo) is in progress. Tools read it to skip cleanup that would fight undo state. Note this is a PROPERTY on `editor.blocks`, unlike the React hook's `isSyncingFromYjs()` method.
isPointerDragActivebooleanReadonly getter — true while a pointer drag interaction is active. Framework adapters read it to defer a programmatic `dispatchChange` mid-drag (core silently drops such a change) and re-dispatch it once the drag ends.