Excalidraw adds a void excalidraw element that embeds the Excalidraw canvas in the editor. The node stores Excalidraw elements and state under data. This page covers kit setup, insertion, persistence shape, and the client-only registry UI.
Features
- Void
excalidrawblock element. - Installed
editor.update.excalidraw.insert(props, options)command. - Excalidraw
elementsand appstatestored on the node. - Dynamic Excalidraw component loading in the React hook.
- Change deduplication before writing canvas data back to Plite.
- Read-only mode through Excalidraw
viewModeEnabled.
Fast Path
Add The Kit
ExcalidrawKit installs ExcalidrawPlugin with the registry ExcalidrawElement.
'use client';
import { ExcalidrawPlugin } from '@platejs/excalidraw/react';
import { ExcalidrawElement } from '@/components/ui/excalidraw-node';
export const ExcalidrawKit = [
ExcalidrawPlugin.withComponent(ExcalidrawElement),
];'use client';
import { ExcalidrawPlugin } from '@platejs/excalidraw/react';
import { ExcalidrawElement } from '@/components/ui/excalidraw-node';
export const ExcalidrawKit = [
ExcalidrawPlugin.withComponent(ExcalidrawElement),
];import { createPlateEditor } from 'platejs/react';
import { ExcalidrawKit } from '@/components/editor/plugins/excalidraw-kit';
export const editor = createPlateEditor({
plugins: ExcalidrawKit,
});import { createPlateEditor } from 'platejs/react';
import { ExcalidrawKit } from '@/components/editor/plugins/excalidraw-kit';
export const editor = createPlateEditor({
plugins: ExcalidrawKit,
});Render The Element
excalidraw-node owns the client component, Excalidraw CSS import, fixed canvas frame, and read-only view mode.
'use client';
import * as React from 'react';
import type { TExcalidrawElement } from '@platejs/excalidraw';
import type { PlateElementProps } from 'platejs/react';
import { useExcalidrawElement } from '@platejs/excalidraw/react';
import { PlateElement, useEditorReadOnly } from 'platejs/react';
import { cn } from '@/lib/utils';
import '@excalidraw/excalidraw/index.css';
export function ExcalidrawElement(
props: PlateElementProps<TExcalidrawElement>
) {
const { children, element } = props;
const readOnly = useEditorReadOnly();
const { Excalidraw, excalidrawProps } = useExcalidrawElement({
element,
});
return (
<PlateElement {...props}>
<div contentEditable={false}>
<div
className={cn(
'mx-auto aspect-video h-[600px] w-[min(100%,600px)] overflow-hidden rounded-sm border'
)}
>
{Excalidraw && (
<Excalidraw {...excalidrawProps} viewModeEnabled={readOnly} />
)}
</div>
</div>
{children}
</PlateElement>
);
}'use client';
import * as React from 'react';
import type { TExcalidrawElement } from '@platejs/excalidraw';
import type { PlateElementProps } from 'platejs/react';
import { useExcalidrawElement } from '@platejs/excalidraw/react';
import { PlateElement, useEditorReadOnly } from 'platejs/react';
import { cn } from '@/lib/utils';
import '@excalidraw/excalidraw/index.css';
export function ExcalidrawElement(
props: PlateElementProps<TExcalidrawElement>
) {
const { children, element } = props;
const readOnly = useEditorReadOnly();
const { Excalidraw, excalidrawProps } = useExcalidrawElement({
element,
});
return (
<PlateElement {...props}>
<div contentEditable={false}>
<div
className={cn(
'mx-auto aspect-video h-[600px] w-[min(100%,600px)] overflow-hidden rounded-sm border'
)}
>
{Excalidraw && (
<Excalidraw {...excalidrawProps} viewModeEnabled={readOnly} />
)}
</div>
</div>
{children}
</PlateElement>
);
}Add An Insert Action
The registry insert toolbar calls the installed Excalidraw command.
import { KEYS } from 'platejs';
export const insertBlockMap = {
[KEYS.excalidraw]: (editor) =>
editor.update.excalidraw.insert({}, { select: true }),
};import { KEYS } from 'platejs';
export const insertBlockMap = {
[KEYS.excalidraw]: (editor) =>
editor.update.excalidraw.insert({}, { select: true }),
};Ownership
| Layer | Owner | What It Does |
|---|---|---|
@platejs/excalidraw | Package | Exports BaseExcalidrawPlugin, TExcalidrawElement, and ExcalidrawDataState. |
@platejs/excalidraw/react | Package | Exports ExcalidrawPlugin, useExcalidrawElement, and React Excalidraw prop types. |
excalidraw-kit | Registry | Adds ExcalidrawPlugin.withComponent(ExcalidrawElement). |
excalidraw-node | Registry UI | Dynamically renders @excalidraw/excalidraw inside a Plate element. |
| App persistence | App code | Stores the Plate value that contains Excalidraw element data. |
ExcalidrawPlugin installs editor.update.excalidraw.insert.
Manual Setup
Install Package
pnpm add @platejs/excalidrawpnpm add @platejs/excalidrawAdd The Plugin
Use the React plugin when the editor renders the Excalidraw canvas.
import { ExcalidrawPlugin } from '@platejs/excalidraw/react';
import { createPlateEditor } from 'platejs/react';
import { ExcalidrawElement } from '@/components/ui/excalidraw-node';
export const editor = createPlateEditor({
plugins: [ExcalidrawPlugin.withComponent(ExcalidrawElement)],
});import { ExcalidrawPlugin } from '@platejs/excalidraw/react';
import { createPlateEditor } from 'platejs/react';
import { ExcalidrawElement } from '@/components/ui/excalidraw-node';
export const editor = createPlateEditor({
plugins: [ExcalidrawPlugin.withComponent(ExcalidrawElement)],
});Insert A Drawing
editor.update.excalidraw.insert inserts after the selected block. Pass
after to use another live node or location. It is a no-op when neither an
explicit reference nor a selection resolves to a block.
editor.update.excalidraw.insert(
{
data: {
elements: [],
state: {
viewBackgroundColor: '#ffffff',
},
},
},
{ select: true }
);editor.update.excalidraw.insert(
{
data: {
elements: [],
state: {
viewBackgroundColor: '#ffffff',
},
},
},
{ select: true }
);Value Shape
TExcalidrawElement is a void element. The drawing payload lives in data, not in text children.
const value = [
{
children: [{ text: '' }],
data: {
elements: [
{
id: 'shape-1',
type: 'rectangle',
x: 100,
y: 100,
},
],
state: {
viewBackgroundColor: '#ffffff',
},
},
type: 'excalidraw',
},
];const value = [
{
children: [{ text: '' }],
data: {
elements: [
{
id: 'shape-1',
type: 'rectangle',
x: 100,
y: 100,
},
],
state: {
viewBackgroundColor: '#ffffff',
},
},
type: 'excalidraw',
},
];| Field | Type | Notes |
|---|---|---|
type | 'excalidraw' | Plugin key and node type from KEYS.excalidraw. |
children | [{ text: '' }] | Required Plite child for the void element. |
data.elements | Excalidraw elements | Stored as partial Excalidraw elements. |
data.state | Excalidraw app state | Stored as imported Excalidraw app state. |
Markdown serialization is not owned by @platejs/excalidraw. Persist the Plate value when you need to keep drawings.
UI Behavior
useExcalidrawElement bridges the Plate node and the Excalidraw React component.
| Surface | Behavior |
|---|---|
| Component loading | Dynamically imports @excalidraw/excalidraw and returns the loaded component. |
| Initial data | Deep-clones element.data.state, element.data.elements, libraryItems, and scrollToContent. |
| Editing | onChange writes { elements, state } back to the node. |
| Deduplication | Uses deep equality to skip writes when canvas data did not change. |
| Read-only mode | Removes the write handler and enables Excalidraw viewModeEnabled. |
| Canvas frame | Registry UI renders a bordered aspect-video frame capped at 600px. |
The registry element imports @excalidraw/excalidraw/index.css, so custom copies need the same stylesheet.
API Reference
| API | Package | Use |
|---|---|---|
BaseExcalidrawPlugin | @platejs/excalidraw | Headless void element plugin. |
ExcalidrawPlugin | @platejs/excalidraw/react | React Excalidraw plugin. |
editor.update.excalidraw.insert(props?, options?) | ExcalidrawPlugin | Inserts a void Excalidraw node after options.after or the selected block. |
useExcalidrawElement(options) | @platejs/excalidraw/react | Returns the dynamically loaded Excalidraw component and props for the registry element. |
TExcalidrawElement | @platejs/excalidraw | Element shape with optional data. |
ExcalidrawDataState | @platejs/excalidraw | Data shape for stored Excalidraw elements and app state. |