Question Details

No question body available.

Tags

javascript editorjs

Answers (1)

Accepted Answer Available
Accepted Answer
February 4, 2026 Score: 3 Rep: 8,414 Quality: High Completeness: 80%

You configured your EditorJS instance with inlineToolbar: ["header", "bold", "italic"], but header is not a inline tool.

https://editorjs.io/inline-tools-api-1/

The inlineToolbar array only accepts tools that implement the Inline Tool API (with static isInline = true)

https://editorjs.io/enable-inline-toolbar/

Names of Tools in inlineToolbar is the keys of Inline Tools plugins that used in tools property of initial config. There are three built-in Inline Tools: link, bold, italic.

While the Header Tool in the @editorjs/header package is documented as a Block Tool. If you check its source code, it does not have static isInline = true — it's designed to create entire blocks, not format inline text selections.

The error thrown:

Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'name')Understand this error

it's because, by including it in the inlineToolbar, you are using the class HeaderTool that doesn't implement the Inline Tool API and the engine just doesn't find a prop that it expects.

So the solution to prevent you having that issue is just removing header from intlineToolbar array:

const raw = JSON.stringify({
  time: Date.now(),
  blocks: [
    {type: "header", data: {text: "heading", level: 2}},
    {type: "paragraph", data: { text: "paragraph with bold and italic"}}
  ],
});

const editor = new EditorJS({ holder: "editor", data: JSON.parse(raw), inlineToolbar: ["bold", "italic"], tools: { header: { class: Header, inlineToolbar: true}, paragraph: { class: Paragraph, inlineToolbar: true}, }, });
#editor {
  border: 1px solid #ccc;
  padding: 10px;
  min-height: 200px;
}

But if your question was literally:

I'm trying to add Heading option as popover buttons like below:

enter image description here

You would need to implement the Inline Tools API on your own doing your custom inline tool that will make a visual trick to mark text contents as "headings" using a custom styling. If you used the would mess up with how block level elements are handled.

This is an example:

class InlineHeadingTool {

// Required static getter - tells EditorJS this is an inline tool (not a block tool) static get isInline() { return true; }

// Tooltip text shown when hovering the button static get title() { return 'Inline Heading'; }

// Tells EditorJS which HTML tags/attributes to preserve when saving // Without this, the span with class would be stripped out static get sanitize() { return { span: { class: 'inline-heading' } }; }

// Called once when tool is instantiated // Receives API object to interact with the editor constructor({ api }) { this.api = api; this.button = null; this.tag = 'SPAN'; this.class = 'inline-heading'; }

// Creates and returns the button element for the inline toolbar // This is what users click render() { this.button = document.createElement('button'); this.button.type = 'button'; this.button.innerHTML = 'H'; this.button.style.cssText = 'font-weight: bold; font-size: 14px; padding: 0 4px; cursor: pointer;'; return this.button; }

// Called when user clicks the button with text selected // Receives the Range object representing the selection // Handles both wrapping (apply) and unwrapping (remove) the style surround(range) { if (!range) return;

const selectedText = range.extractContents(); const existingSpan = this.api.selection.findParentTag(this.tag, this.class);

if (existingSpan) { // Unwrap - remove the heading style this.unwrap(existingSpan); } else { // Wrap selection in styled span this.wrap(range, selectedText); } }

// Helper method - wraps selected text in a styled wrap(range, selectedText) { const span = document.createElement(this.tag); span.classList.add(this.class); span.appendChild(selectedText); range.insertNode(span); this.api.selection.expandToTag(span); }

// Helper method - removes the , leaving just the text content unwrap(span) { const text = document.createDocumentFragment(); while (span.firstChild) { text.appendChild(span.firstChild); } span.parentNode.replaceChild(text, span); }

// Called when inline toolbar opens to check if selection is already styled // Used to highlight/toggle the button's active state checkState() { const span = this.api.selection.findParentTag(this.tag, this.class); this.button.classList.toggle('active', !!span); if (span) { this.button.style.background = '#ddd'; } else { this.button.style.background = ''; } return !!span; } }

const editor = new EditorJS({ holder: 'editor', inlineToolbar: ['inlineHeading', 'bold', 'italic'], tools: { inlineHeading: InlineHeadingTool, paragraph: { class: Paragraph, inlineToolbar: true } }, data: { blocks: [{ type: 'paragraph', data: { text: 'This is a paragraph text as a demo to see how the inline heading works.' } }] } });
span.inline-heading {
  font-size: 1.4em;
  font-weight: bold;
}