Skip to main content
Blocks are the top-level content units in Yoopta Editor. Each block represents a distinct piece of content like a paragraph, heading, image, or code block. Blocks can be nested, reordered, and transformed independently.

Block Structure

Every block in Yoopta Editor follows this structure:
packages/core/editor/src/editor/types.ts
type YooptaBlockData = {
  id: string;              // Unique block identifier
  type: string;            // Block type (PascalCase: "Paragraph", "HeadingOne")
  value: SlateElement[];   // Array of Slate elements
  meta: YooptaBlockBaseMeta;
};

type YooptaBlockBaseMeta = {
  order: number;                              // Position in document (0-based)
  depth: number;                              // Nesting level (0 = root)
  align?: 'left' | 'center' | 'right';       // Text alignment
};
Block types use PascalCase (“Paragraph”, “HeadingOne”) while element types within blocks use kebab-case (“paragraph”, “heading-one”). This convention helps distinguish between the block API and Slate element API.

Block Operations

Yoopta Editor provides two ways to perform block operations:
  1. Editor instance methods: editor.insertBlock(), editor.deleteBlock(), etc.
  2. Blocks namespace: Blocks.insertBlock(editor, ...), Blocks.deleteBlock(editor, ...)
Both approaches work identically. The namespace API is useful when you don’t have direct access to the editor instance.

Inserting Blocks

Insert a new block at a specific position:
// Using editor instance
const blockId = editor.insertBlock('Paragraph', {
  at: 0,           // Insert at position 0
  focus: true,     // Focus the new block
});

// Using Blocks namespace
import { Blocks } from '@yoopta/editor';

Blocks.insertBlock(editor, 'Paragraph', {
  at: editor.path.current,
  focus: false,
});

Insert Block Options

packages/core/editor/src/editor/blocks/insertBlock.ts
type InsertBlockOptions = {
  at?: YooptaPathIndex;           // Position to insert (default: end)
  focus?: boolean;                 // Focus after insert (default: false)
  blockData?: Partial<YooptaBlockData>;  // Custom block data
  elements?: SlateElement;         // Custom element structure
};

Inserting with Custom Content

Use the editor.y builder to create custom element structures:
editor.insertBlock('Paragraph', {
  focus: true,
  elements: editor.y('paragraph', {
    children: [
      editor.y.text('Hello '),
      editor.y.text('World', { bold: true }),
      editor.y.text('!'),
    ]
  })
});
The elements option is powerful for creating blocks with pre-filled content. This is commonly used when inserting templates or converting between block types.

Complex Block Structures

For plugins with nested elements (like accordions or steps):
editor.insertBlock('Accordion', {
  elements: editor.y('accordion-list', {
    children: [
      editor.y('accordion-list-item', {
        props: { isExpanded: false },
        children: [
          editor.y('accordion-list-item-heading', {
            children: [editor.y.text('Section 1')]
          }),
          editor.y('accordion-list-item-content', {
            children: [
              editor.y('paragraph', {
                children: [editor.y.text('Content goes here')]
              })
            ]
          })
        ]
      })
    ]
  })
});

Updating Blocks

Update block properties or content:
// Update block type
editor.updateBlock(blockId, {
  type: 'HeadingOne'
});

// Update block metadata
editor.updateBlock(blockId, {
  meta: {
    align: 'center',
    depth: 1
  }
});

// Update block value (Slate elements)
editor.updateBlock(blockId, {
  value: [
    editor.y('heading-one', {
      children: [editor.y.text('New content')]
    })
  ]
});

Deleting Blocks

// Delete a specific block
editor.deleteBlock(blockId);

// Delete with options
import { Blocks } from '@yoopta/editor';

Blocks.deleteBlock(editor, blockId, {
  focus: true  // Focus the next block after deletion
});
Deleting a block is permanent and cannot be undone programmatically. Users can still undo with Ctrl+Z (Cmd+Z), but your code should handle deletion carefully.

Moving Blocks

Move blocks to different positions:
// Move block to position 5
editor.moveBlock(blockId, {
  to: 5
});

// Move block up one position
const block = editor.getBlock({ id: blockId });
if (block && block.meta.order > 0) {
  editor.moveBlock(blockId, {
    to: block.meta.order - 1
  });
}

Duplicating Blocks

Create a copy of a block:
// Duplicate block at the next position
const newBlockId = editor.duplicateBlock(blockId);

// Duplicate with custom position
const newBlockId = editor.duplicateBlock(blockId, {
  at: 10,
  focus: true
});

Toggling Block Types

Change a block’s type while preserving its content:
// Convert paragraph to heading
editor.toggleBlock(blockId, {
  type: 'HeadingOne'
});

// Toggle preserves text formatting and content
The toggleBlock operation intelligently converts content between block types, preserving text formatting (bold, italic, etc.) while adapting to the new block’s structure.

Splitting Blocks

Split a block at the current cursor position:
// Split at current cursor position
const newBlockId = editor.splitBlock();

// Split with custom options
const newBlockId = editor.splitBlock({
  at: blockId,
  focus: true
});
This creates a new block with the content after the cursor.

Merging Blocks

Merge two adjacent blocks:
// Merge current block with previous
editor.mergeBlock({
  blockId: currentBlockId,
  targetBlockId: previousBlockId
});
Content from the source block is appended to the target block.

Managing Block Depth

Increase or decrease nesting level:
// Increase depth (indent)
editor.increaseBlockDepth(blockId);

// Decrease depth (outdent)
editor.decreaseBlockDepth(blockId);

// Check current depth
const block = editor.getBlock({ id: blockId });
console.log('Current depth:', block.meta.depth);
Block depth is used for nested content like lists, tasks, and hierarchical structures. The depth value starts at 0 (root level) and increases for nested items.

Focusing Blocks

Set focus to a specific block:
// Focus block and place cursor at start
editor.focusBlock(blockId);

// Focus with options
editor.focusBlock(blockId, {
  at: 'end'  // Place cursor at end of block
});

Getting Block Data

Retrieve block information:
// Get by block ID
const block = editor.getBlock({ id: blockId });

if (block) {
  console.log('Type:', block.type);
  console.log('Order:', block.meta.order);
  console.log('Depth:', block.meta.depth);
  console.log('Elements:', block.value);
}

// Get current block
const currentBlock = editor.getBlock({ 
  id: editor.path.current 
});

Block Lifecycle

Plugins can hook into block creation and destruction:
import { createYooptaPlugin } from '@yoopta/editor';

const MyPlugin = createYooptaPlugin({
  type: 'MyPlugin',
  elements: { /* ... */ },
  lifecycle: {
    beforeCreate: (editor) => {
      // Return custom initial element structure
      return editor.y('my-plugin-element', {
        children: [editor.y.text('Initial content')]
      });
    },
    onCreate: (editor, blockId) => {
      // Called after block is created
      console.log('Block created:', blockId);
    },
    onDestroy: (editor, blockId) => {
      // Cleanup when block is deleted
      console.log('Block destroyed:', blockId);
    }
  }
});

Working with Content Value

The entire editor content is a record of blocks:
// Get all blocks
const content = editor.getEditorValue();

// Iterate over blocks
Object.values(content).forEach(block => {
  console.log(`Block ${block.id} (${block.type}) at position ${block.meta.order}`);
});

// Sort blocks by order
const sortedBlocks = Object.values(content)
  .sort((a, b) => a.meta.order - b.meta.order);

// Set new content
editor.setEditorValue({
  [blockId1]: block1,
  [blockId2]: block2,
});
When setting editor value with setEditorValue, make sure all blocks have correct order values starting from 0 with no gaps.

Batch Operations

Perform multiple operations efficiently:
editor.batchOperations(() => {
  // Multiple operations in a single transaction
  editor.insertBlock('Paragraph');
  editor.insertBlock('HeadingOne');
  editor.insertBlock('Paragraph');
});

// This triggers only one 'change' event instead of three

Block Path and Selection

Track which block is currently active:
// Get current block path
const path = editor.path;
console.log('Current block index:', path.current);
console.log('Selected blocks:', path.selected);
console.log('Selection source:', path.source);

// Set path programmatically
editor.setPath({
  current: 5,
  selected: [3, 4, 5],
  source: 'keyboard'
});

Using the Blocks Namespace

The Blocks namespace provides static functions:
import { Blocks } from '@yoopta/editor';

// All operations are available:
Blocks.insertBlock(editor, 'Paragraph', { ... });
Blocks.deleteBlock(editor, blockId, { ... });
Blocks.updateBlock(editor, blockId, { ... });
Blocks.moveBlock(editor, blockId, { ... });
Blocks.duplicateBlock(editor, blockId, { ... });
Blocks.toggleBlock(editor, blockId, { ... });
Blocks.focusBlock(editor, blockId, { ... });
Blocks.splitBlock(editor, { ... });
Blocks.mergeBlock(editor, { ... });
Blocks.increaseBlockDepth(editor, blockId, { ... });
Blocks.decreaseBlockDepth(editor, blockId, { ... });
Blocks.getBlock(editor, { ... });
Blocks.buildBlockData(editor, { ... });
The namespace API is particularly useful when building utilities or extensions that operate on editors passed as parameters.

Next Steps

Element System

Learn about elements within blocks

Plugin System

Create custom block types

Events

Listen to block changes

Editor Instance

Back to editor API reference

Build docs developers (and LLMs) love