---
title: "Свой блочный инструмент для редактора Blok"
description: "Реализация интерфейса BlockTool: render, save, validate, toolbox, pasteConfig и sanitize на рабочем примере."
source: https://blokeditor.com/ru/docs/custom-block-tool/
lastmod: 2026-09-07
---

Фреймворк JavaScript

Начало работы Создание собственного блок-инструмента

# Как создать собственный блочный инструмент

Создайте блок-инструмент с нуля — блок-выноску, которая отрисовывается, редактируется и сохраняется как любой встроенный блок.

Обновлено 30 июн. 2026 г. [Редактировать на GitHub](https://github.com/JackUait/blok/blob/main/docs/src/components/api/HowToCustomToolContent.tsx)

Каждый встроенный блок — это просто инструмент, реализующий интерфейс `BlockTool`, и вы можете добавить свой. Здесь вы создадите блок Callout: редактируемую выноску, которая сохраняет свой текст и загружает его обратно, как любой другой блок.

1

## Напишите класс инструмента

Блок-инструменту нужны три вещи: статический геттер `toolbox`, чтобы он появился в меню `/`, метод `render()`, возвращающий элемент блока, и метод `save()`, возвращающий данные для сохранения.

TypeScript

```
// callout-tool.ts
export class CalloutTool {
  private data: { text: string };

  // Shows the tool in the "/" menu. `section` groups the entry under a
  // labeled heading ('basic' | 'media' | 'database' | 'advanced'); omit it
  // to list the entry in the trailing unlabeled group.
  static get toolbox() {
    return { title: 'Callout', icon: '💡', section: 'basic' };
  }

  constructor({ data }: { data: { text?: string } }) {
    this.data = { text: data.text ?? '' };
  }

  // Return the element Blok mounts for this block.
  render() {
    const box = document.createElement('div');
    box.classList.add('callout');
    box.contentEditable = 'true';
    box.textContent = this.data.text;
    return box;
  }

  // Return the data Blok stores when the editor is saved.
  save(block: HTMLElement) {
    return { text: block.textContent ?? '' };
  }
}
```

2

## Зарегистрируйте его в редакторе

Передайте класс в конфигурацию `tools`. Ключ, который вы зададите, станет значением `type` блока в сохранённых данных.

TypeScript

```
import { Blok } from '@bloklabs/core';
import { Paragraph } from '@bloklabs/core/tools';
import { CalloutTool } from './callout-tool';

const editor = new Blok({
  holder: 'editor',
  tools: {
    paragraph: Paragraph, // the default block every empty editor starts with
    callout: CalloutTool, // the key becomes the block's `type`
  },
});
```

3

## Используйте и сохраните

Откройте меню `/`, выберите Callout, введите текст в выноску и вызовите `save()`. Ваш блок появится в результате рядом со встроенными.

TypeScript

```
const data = await editor.save();

// Your block round-trips exactly like a built-in one:
// {
//   id: 'x9k2f1',
//   type: 'callout',
//   data: { text: 'Heads up — this is a callout.' },
// }
```

## Что дальше

Добавьте метод `validate()`, чтобы отбрасывать пустые блоки, меню настроек через `renderSettings()` или обработку вставки через `onPaste`. Интерфейс `BlockTool` покрывает весь жизненный цикл — `rendered()`, `updated()`, `moved()` и `removed()`.

TypeScript

```
// callout-tool.ts (extended)
export class CalloutTool {
  // ...constructor unchanged; render() now keeps its element as `this.box`.

  // Opt into read-only mode. Without it, creating the editor with
  // `readOnly: true` — or calling `readOnly.toggle(true)` — throws
  // "To enable read-only mode all connected tools should support it".
  static isReadOnlySupported = true;

  save(block: HTMLElement) {
    return { text: block.textContent ?? '' };
  }

  // Drop empty callouts when the editor saves.
  validate(savedData: { text: string }) {
    return savedData.text.trim().length > 0;
  }

  // Optional: flip the DOM in place. If any registered tool lacks
  // setReadOnly(), every toggle re-runs a full save/clear/render cycle.
  setReadOnly(state: boolean) {
    this.box.contentEditable = String(!state);
  }
}

// text-color-tune.ts — a block tune adds a control to the settings menu.
export class TextColorTune {
  static isTune = true;

  render() {
    return { title: 'Text color', icon: '🎨', onActivate: () => {/* recolor */} };
  }
}
```

TypeScript

```
import { Blok } from '@bloklabs/core';
import { Paragraph } from '@bloklabs/core/tools';
import { CalloutTool } from './callout-tool';
import { TextColorTune } from './text-color-tune';

const editor = new Blok({
  holder: 'editor',
  tools: {
    paragraph: Paragraph, // the default block every empty editor starts with
    callout: { class: CalloutTool, tunes: ['textColor'] },
    // Register the tune as a tool so a block can list it by name.
    textColor: TextColorTune,
  },
});
```

`validate()` выполняется перед тем, как разрешится `save()`, поэтому пустая выноска никогда не попадёт в результат. Поддержка режима только для чтения подключается явно: без `static isReadOnlySupported = true` создание редактора с `readOnly: true` — или вызов `readOnly.toggle(true)` — выбросит ошибку «To enable read-only mode all connected tools should support it». Дополнительно добавленный `setReadOnly(state)` позволяет блоку переключаться на месте; если хотя бы у одного зарегистрированного инструмента его нет, каждое переключение прогоняет полный цикл save/clear/render. Тюны подключаются по желанию для каждого инструмента — зарегистрируйте класс тюна как инструмент (класс со `static isTune = true`), затем укажите его имя в `tunes: [...]`, и Blok добавит его в меню настроек блока.

Полный контракт инструмента смотрите в [Tools API](https://blokeditor.com/ru/docs/tools-api/) и загляните в [BlockData](https://blokeditor.com/ru/docs/block-data/) за точной формой данных, которые возвращает ваш `save()`.
