> ## Documentation Index
> Fetch the complete documentation index at: https://superdoc-caio-pizzol-docs-ai-core-preset.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# BlockNode extension

export const SourceCodeLink = ({extension, path}) => {
  const githubPath = path || `packages/super-editor/src/editors/v1/extensions/${extension.toLowerCase()}`;
  const githubUrl = `https://github.com/superdoc-dev/superdoc/tree/main/${githubPath}`;
  return <div>
      <p>
        <a href={githubUrl} target="_blank" rel="noopener noreferrer">
          View on GitHub →
        </a>
      </p>
    </div>;
};

export const SuperDocEditor = ({html = '<p>Start editing...</p>', height = '400px', maxHeight = '400px', onReady = null, showExport = true, customButtons = null}) => {
  const [ready, setReady] = useState(false);
  const editorRef = useRef(null);
  const containerIdRef = useRef(`editor-${Math.random().toString(36).substr(2, 9)}`);
  const DEV_DIST_URL = 'http://localhost:9094/dist';
  const UNPKG_DIST_URL = 'https://unpkg.com/superdoc@latest/dist';
  const getBaseUrl = async () => {
    const isDev = typeof window !== 'undefined' && window.location.hostname === 'localhost';
    if (isDev) {
      try {
        const res = await fetch(`${DEV_DIST_URL}/superdoc.min.js`, {
          method: 'HEAD'
        });
        if (res.ok) {
          console.info('[SuperDoc Docs] Using local build from', DEV_DIST_URL);
          return DEV_DIST_URL;
        }
        console.warn('[SuperDoc Docs] Local dev server returned', res.status, '- falling back to unpkg');
      } catch (err) {
        console.warn('[SuperDoc Docs] Local dev server not reachable: falling back to unpkg.', 'Run `pnpm dev:docs` from the repo root to use your local build.', err.message);
      }
    }
    return UNPKG_DIST_URL;
  };
  const ensureStyle = baseUrl => {
    const styleHref = `${baseUrl}/style.css`;
    if (document.querySelector(`link[href="${styleHref}"]`)) return;
    const link = document.createElement('link');
    link.rel = 'stylesheet';
    link.href = styleHref;
    document.head.appendChild(link);
  };
  const loadSuperDoc = baseUrl => {
    if (window.SuperDoc) return Promise.resolve();
    const scriptSrc = `${baseUrl}/superdoc.min.js`;
    const existingScript = document.querySelector(`script[src="${scriptSrc}"]`);
    if (existingScript) {
      if (window.SuperDoc) return Promise.resolve();
      return new Promise((resolve, reject) => {
        existingScript.addEventListener('load', resolve, {
          once: true
        });
        existingScript.addEventListener('error', reject, {
          once: true
        });
      });
    }
    return new Promise((resolve, reject) => {
      const script = document.createElement('script');
      script.src = scriptSrc;
      script.onload = resolve;
      script.onerror = reject;
      document.body.appendChild(script);
    });
  };
  const initEditor = () => {
    setTimeout(() => {
      if (!window.SuperDoc) return;
      if (!document.getElementById(containerIdRef.current)) return;
      if (editorRef.current) return;
      editorRef.current = new window.SuperDoc({
        selector: `#${containerIdRef.current}`,
        html,
        rulers: true,
        contained: true,
        onReady: () => {
          setReady(true);
          if (onReady) onReady(editorRef.current);
        }
      });
    }, 100);
  };
  useEffect(() => {
    let cancelled = false;
    const boot = async () => {
      try {
        const baseUrl = await getBaseUrl();
        ensureStyle(baseUrl);
        await loadSuperDoc(baseUrl);
        if (!cancelled) initEditor();
      } catch (error) {
        console.error('Failed to boot SuperDoc:', error);
      }
    };
    boot();
    return () => {
      cancelled = true;
      editorRef.current?.destroy?.();
      editorRef.current = null;
    };
  }, []);
  const exportDocx = () => {
    if (editorRef.current?.export) {
      editorRef.current.export();
    }
  };
  return <div className="border rounded-lg bg-white overflow-hidden">
      {ready && (showExport || customButtons) && <div className="px-3 py-2 bg-gray-50 border-b">
          {customButtons && <div className="space-y-1 mb-2">
              {customButtons.map((row, rowIndex) => <div key={rowIndex} className="flex gap-1">
                  {row.map((btn, i) => <button key={i} onClick={() => btn.onClick(editorRef.current)} className={btn.className || 'flex-1 px-2 py-1 bg-gray-100 text-gray-700 text-xs rounded hover:bg-gray-200'}>
                      {btn.label}
                    </button>)}
                </div>)}
            </div>}
          {showExport && <div className="text-right">
              <button onClick={exportDocx} className="px-3 py-1 bg-blue-500 text-white text-xs rounded hover:bg-blue-600">
                Export DOCX
              </button>
            </div>}
        </div>}
      <div id={containerIdRef.current} style={{
    height,
    maxHeight,
    paddingLeft: '5px'
  }} />
      <style jsx>{`
        #${containerIdRef.current} .superdoc__layers {
          max-width: 660px !important;
        }
        #${containerIdRef.current} .super-editor {
          max-width: 100% !important;
          width: 100% !important;
          color: #000;
        }
        #${containerIdRef.current} .editor-element {
          width: 100% !important;
          min-width: unset !important;
          transform: none !important;
        }
        #${containerIdRef.current} .editor-element {
          h1,
          h2,
          h3,
          h4,
          h5,
          strong {
            color: #000;
          }
        }
      `}</style>
    </div>;
};

Automatically tracks block-level nodes with unique IDs for precise document manipulation.

Essential for collaborative editing, change tracking, and programmatic document updates.

<SuperDocEditor
  html={`<h1>Document Title</h1><p>First paragraph with some content.</p><p>Second paragraph to demonstrate block operations.</p><h2>Section Heading</h2><p>Another paragraph in the section.</p>`}
  height="400px"
  onReady={(superdoc) => {
// Make editor accessible for console experiments
window.testEditor = superdoc?.activeEditor || superdoc?.editor;
}}
  customButtons={[
[
  {
    label: 'Show Block IDs',
    onClick: (superdoc) => {
      const editor = superdoc?.activeEditor || superdoc?.editor
      if (!editor?.helpers) return
      
      const blocks = editor.helpers.blockNode.getBlockNodes()
      const info = blocks.map((b, i) => 
        `${i + 1}. ${b.node.type.name}: "${b.node.textContent.substring(0, 30)}..." (ID: ${b.node.attrs.sdBlockId?.substring(0, 8)}...)`
      ).join('\n')
      
      alert(`Found ${blocks.length} blocks:\n\n${info}`)
    }
  },
  {
    label: 'Count by Type',
    onClick: (superdoc) => {
      const editor = superdoc?.activeEditor || superdoc?.editor
      if (!editor?.helpers) return
      
      const paragraphs = editor.helpers.blockNode.getBlockNodesByType('paragraph')
      const headings = editor.helpers.blockNode.getBlockNodesByType('heading')
      
      alert(`Document structure:\n• ${paragraphs.length} paragraphs\n• ${headings.length} headings`)
    }
  }
],
[
  {
    label: 'Center First Block',
    onClick: (superdoc) => {
      const editor = superdoc?.activeEditor || superdoc?.editor
      if (!editor?.helpers || !editor?.commands) return
      
      const blocks = editor.helpers.blockNode.getBlockNodes()
      if (!blocks.length) return
      
      const firstId = blocks[0].node.attrs.sdBlockId
      editor.commands.updateBlockNodeAttributes(firstId, { 
        textAlign: 'center' 
      })
    }
  },
  {
    label: 'Delete Last Block',
    onClick: (superdoc) => {
      const editor = superdoc?.activeEditor || superdoc?.editor
      if (!editor?.helpers || !editor?.commands) return
      
      const blocks = editor.helpers.blockNode.getBlockNodes()
      if (!blocks.length) return
      
      const lastId = blocks[blocks.length - 1].node.attrs.sdBlockId
      if (confirm('Delete the last block?')) {
        editor.commands.deleteBlockNodeById(lastId)
      }
    }
  },
  {
    label: 'Replace Second Block',
    onClick: (superdoc) => {
      const editor = superdoc?.activeEditor || superdoc?.editor
      if (!editor?.helpers || !editor?.commands) return
      
      const blocks = editor.helpers.blockNode.getBlockNodes()
      if (blocks.length < 2) return
      
      const secondBlock = blocks[1]
      const id = secondBlock.node.attrs.sdBlockId
      const type = secondBlock.node.type.name
      
      const newNode = editor.schema.nodes[type].create(
        { sdBlockId: id + '-new' },
        editor.schema.text('✨ This block was replaced programmatically!')
      )
      
      editor.commands.replaceBlockNodeById(id, newNode)
    }
  }
],
[
  {
    label: 'Blocks in Selection',
    onClick: (superdoc) => {
      const editor = superdoc?.activeEditor || superdoc?.editor
      if (!editor?.helpers) return
      
      const { from, to } = editor.state.selection
      const blocks = editor.helpers.blockNode.getBlockNodesInRange(from, to)
      
      if (blocks.length) {
        const types = blocks.map(b => b.node.type.name).join(', ')
        alert(`${blocks.length} blocks in selection:\n${types}`)
      } else {
        alert('No blocks in current selection')
      }
    }
  }
]
]}
/>

## How it works

Every block-level node (paragraphs, headings, etc.) automatically receives a unique `sdBlockId` attribute. This enables:

1. **Precise targeting** - Manipulate specific blocks even as document changes
2. **Change tracking** - Know exactly which blocks were modified
3. **Collaborative editing** - Reference blocks consistently across clients
4. **Programmatic updates** - Update document structure via APIs

<Warning>
  `sdBlockId` is regenerated on every document load. If you need cross-session block references (e.g., headless CLI pipelines, multi-step AI workflows), use the [Document API](/document-api/overview) instead. `editor.doc.find()` returns addresses whose `nodeId` prefers DOCX-native `paraId` for imported blocks. This is best-effort stable, not a permanent global ID: Word or other tools can rewrite IDs during structural changes, and SuperDoc may rewrite duplicates on import.
</Warning>

## Use case

* **Document APIs** - Build REST APIs that manipulate specific blocks
* **Collaboration** - Track who edited which blocks in real-time
* **Comments & Annotations** - Attach metadata to specific blocks
* **Version Control** - Diff documents at the block level
* **Templates** - Replace placeholder blocks with dynamic content

## Commands

### `replaceBlockNodeById`

Replace a block node by its ID with new content

<Note>
  The replacement node should have the same type as the original
</Note>

**Example:**

<CodeGroup>
  ```javascript Usage theme={null}
  const newParagraph = editor.schema.nodes.paragraph.create({}, editor.schema.text('New content'))
  editor.commands.replaceBlockNodeById('block-123', newParagraph)
  ```

  ```javascript Full Example theme={null}
  import { SuperDoc } from 'superdoc';
  import 'superdoc/style.css';

  const superdoc = new SuperDoc({
    selector: '#editor',
    document: yourFile,
    onReady: (superdoc) => {
      const editor = superdoc.activeEditor;
      const newParagraph = editor.schema.nodes.paragraph.create({}, editor.schema.text('New content'))
      editor.commands.replaceBlockNodeById('block-123', newParagraph)
    },
  });
  ```
</CodeGroup>

**Parameters:**

<ParamField path="id" type="string" required>
  The sdBlockId of the node to replace
</ParamField>

<ParamField path="contentNode" type="ProseMirrorNode" required>
  The replacement ProseMirror node
</ParamField>

### `deleteBlockNodeById`

Delete a block node by its ID

<Note>
  Completely removes the node from the document
</Note>

**Example:**

<CodeGroup>
  ```javascript Usage theme={null}
  editor.commands.deleteBlockNodeById('block-123')
  ```

  ```javascript Full Example theme={null}
  import { SuperDoc } from 'superdoc';
  import 'superdoc/style.css';

  const superdoc = new SuperDoc({
    selector: '#editor',
    document: yourFile,
    onReady: (superdoc) => {
      const editor = superdoc.activeEditor;
      editor.commands.deleteBlockNodeById('block-123')
    },
  });
  ```
</CodeGroup>

**Parameters:**

<ParamField path="id" type="string" required>
  The sdBlockId of the node to delete
</ParamField>

### `updateBlockNodeAttributes`

Update attributes of a block node by its ID

<Note>
  Merges new attributes with existing ones
</Note>

**Example:**

<CodeGroup>
  ```javascript Usage theme={null}
  editor.commands.updateBlockNodeAttributes('block-123', { textAlign: 'center' })
  ```

  ```javascript Full Example theme={null}
  import { SuperDoc } from 'superdoc';
  import 'superdoc/style.css';

  const superdoc = new SuperDoc({
    selector: '#editor',
    document: yourFile,
    onReady: (superdoc) => {
      const editor = superdoc.activeEditor;
      editor.commands.updateBlockNodeAttributes('block-123', { textAlign: 'center' })
    },
  });
  ```
</CodeGroup>

**Parameters:**

<ParamField path="id" type="string" required>
  The sdBlockId of the node to update
</ParamField>

<ParamField path="attrs" type="Object" required>
  Attributes to update
</ParamField>

## Helpers

### `getBlockNodes`

Get all block nodes in the document

**Example:**

<CodeGroup>
  ```javascript Usage theme={null}
  const blocks = editor.helpers.blockNode.getBlockNodes()
  console.log(`Found ${blocks.length} block nodes`)
  ```

  ```javascript Full Example theme={null}
  import { SuperDoc } from 'superdoc';
  import 'superdoc/style.css';

  const superdoc = new SuperDoc({
    selector: '#editor',
    document: yourFile,
    onReady: (superdoc) => {
      const editor = superdoc.activeEditor;
      const blocks = editor.helpers.blockNode.getBlockNodes()
      console.log(`Found ${blocks.length} block nodes`)
    },
  });
  ```
</CodeGroup>

**Returns:**

<ResponseField name="return" type="Array<BlockNodeInfo>" required>
  See [BlockNodeInfo](#blocknodeinfo) type definition
</ResponseField>

### `getBlockNodeById`

Get a specific block node by its ID

**Example:**

<CodeGroup>
  ```javascript Usage theme={null}
  const block = editor.helpers.blockNode.getBlockNodeById('block-123')
  if (block.length) console.log('Found:', block[0].node.type.name)
  ```

  ```javascript Full Example theme={null}
  import { SuperDoc } from 'superdoc';
  import 'superdoc/style.css';

  const superdoc = new SuperDoc({
    selector: '#editor',
    document: yourFile,
    onReady: (superdoc) => {
      const editor = superdoc.activeEditor;
      const block = editor.helpers.blockNode.getBlockNodeById('block-123')
      if (block.length) console.log('Found:', block[0].node.type.name)
    },
  });
  ```
</CodeGroup>

**Parameters:**

<ParamField path="id" type="string" required>
  The sdBlockId to search for
</ParamField>

**Returns:**

<ResponseField name="return" type="Array<BlockNodeInfo>" required>
  See [BlockNodeInfo](#blocknodeinfo) type definition
</ResponseField>

### `getBlockNodesByType`

Get all block nodes of a specific type

**Example:**

<CodeGroup>
  ```javascript Usage theme={null}
  const paragraphs = editor.helpers.blockNode.getBlockNodesByType('paragraph')
  const headings = editor.helpers.blockNode.getBlockNodesByType('heading')
  ```

  ```javascript Full Example theme={null}
  import { SuperDoc } from 'superdoc';
  import 'superdoc/style.css';

  const superdoc = new SuperDoc({
    selector: '#editor',
    document: yourFile,
    onReady: (superdoc) => {
      const editor = superdoc.activeEditor;
      const paragraphs = editor.helpers.blockNode.getBlockNodesByType('paragraph')
      const headings = editor.helpers.blockNode.getBlockNodesByType('heading')
    },
  });
  ```
</CodeGroup>

**Parameters:**

<ParamField path="type" type="string" required>
  The node type name (e.g., 'paragraph', 'heading')
</ParamField>

**Returns:**

<ResponseField name="return" type="Array<BlockNodeInfo>" required>
  See [BlockNodeInfo](#blocknodeinfo) type definition
</ResponseField>

### `getBlockNodesInRange`

Get all block nodes within a position range

**Example:**

<CodeGroup>
  ```javascript Usage theme={null}
  const selection = editor.state.selection
  const blocksInSelection = editor.helpers.blockNode.getBlockNodesInRange(
    selection.from,
    selection.to
  )
  ```

  ```javascript Full Example theme={null}
  import { SuperDoc } from 'superdoc';
  import 'superdoc/style.css';

  const superdoc = new SuperDoc({
    selector: '#editor',
    document: yourFile,
    onReady: (superdoc) => {
      const editor = superdoc.activeEditor;
      const selection = editor.state.selection
      const blocksInSelection = editor.helpers.blockNode.getBlockNodesInRange(
        selection.from,
        selection.to
      )
    },
  });
  ```
</CodeGroup>

**Parameters:**

<ParamField path="from" type="number" required>
  Start position
</ParamField>

<ParamField path="to" type="number" required>
  End position
</ParamField>

**Returns:**

<ResponseField name="return" type="Array<BlockNodeInfo>" required>
  See [BlockNodeInfo](#blocknodeinfo) type definition
</ResponseField>

## Types

### `BlockNodeInfo`

Block node information object

<Expandable title="Properties">
  <ResponseField name="node" type="ProseMirrorNode" required>
    The block node
  </ResponseField>

  <ResponseField name="pos" type="number" required>
    Position in the document
  </ResponseField>
</Expandable>

## Source code

<SourceCodeLink path="packages/super-editor/src/editors/v1/extensions/block-node/block-node.js" />
