Command Palette

Search for a command to run...

Markdown

Convert Plate content to Markdown and vice-versa.

The @udecode/plate-markdown package provides robust, two-way conversion between Markdown and Plate's content structure.

Loading...
Files
components/markdown-to-slate-demo.tsx
'use client';

import * as React from 'react';

import {
  MarkdownPlugin,
  remarkMdx,
  remarkMention,
} from '@udecode/plate-markdown';
import { Plate, usePlateEditor } from '@udecode/plate/react';
import remarkEmoji from 'remark-emoji';
import remarkGfm from 'remark-gfm';
import remarkMath from 'remark-math';

import { editorPlugins } from '@/components/editor/plugins/editor-plugins';
import { editorComponents } from '@/components/editor/use-create-editor';
import { useDebounce } from '@/hooks/use-debounce';
import { Editor, EditorContainer } from '@/components/ui/editor';

const initialMarkdown = `## Basic Markdown

> The following node and marks is supported by the Markdown standard.

Format text with **bold**, _italic_, _**combined styles**_, ~~strikethrough~~, \`code\` formatting, and [hyperlinks](https://en.wikipedia.org/wiki/Hypertext).

\`\`\`javascript
// Use code blocks to showcase code snippets
function greet() {
  console.info("Hello World!")
}
\`\`\`

- Simple lists for organizing content

1. Numbered lists for sequential steps

| **Plugin**  | **Element** | **Inline** | **Void** |
| ----------- | ----------- | ---------- | -------- |
| **Heading** |             |            | No       |
| **Image**   | Yes         | No         | Yes      |
| **Mention** | Yes         | Yes        | Yes      |

![](https://images.unsplash.com/photo-1712688930249-98e1963af7bd?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D)

- [x] Completed tasks

- [ ] Pending tasks

---

## Advanced Features

<callout>
The following node and marks are not supported in Markdown but can be serialized and deserialized using MDX or specialized UnifiedJS plugins.
</callout>

Advanced marks: <kbd>⌘ + B</kbd>,<u>underlined</u>, <mark>highlighted</mark> text, <span style="color: #93C47D;">colored text</span> and <span style="background-color: #6C9EEB;">background highlights</span> for visual emphasis.

Superscript like E=mc<sup>2</sup> and subscript like H<sub>2</sub>O demonstrate mathematical and chemical notation capabilities.

Add mentions like @BB-8, dates (<date>2025-05-08</date>), and math formulas ($E=mc^2$).

The table of contents feature automatically generates document structure for easy navigation.

<toc />

Math formula support makes displaying complex mathematical expressions simple.

$$
\\frac{-b \\pm \\sqrt{b^2 - 4ac}}{2a}
$$

Multi-column layout features enable richer page designs and content layouts.

<column_group layout="[50,50]">
<column width="50%">
left
</column>

  <column width="50%">
    right
  </column>
</column_group>

PDF embedding makes document referencing simple and intuitive.
<file name="sample.pdf" align="center" src="https://s26.q4cdn.com/900411403/files/doc_downloads/test.pdf" width="80%" isUpload="true" />

Audio players can be embedded directly into documents, supporting online audio resources.
<audio align="center" src="https://samplelib.com/lib/preview/mp3/sample-3s.mp3" width="80%" />

Video playback features support embedding various online video resources, enriching document content.
<video align="center" src="https://videos.pexels.com/video-files/6769791/6769791-uhd_2560_1440_24fps.mp4" width="80%" isUpload="true" />

:smile: :heart:
`;

export default function MarkdownDemo() {
  const [markdownValue, setMarkdownValue] = React.useState(initialMarkdown);
  const debouncedMarkdownValue = useDebounce(markdownValue, 300);

  const markdownEditor = usePlateEditor({
    plugins: [],
    value: [{ children: [{ text: markdownValue }], type: 'p' }],
  });

  const editor = usePlateEditor(
    {
      components: editorComponents,
      plugins: editorPlugins,
      value: (editor) =>
        editor.getApi(MarkdownPlugin).markdown.deserialize(initialMarkdown, {
          remarkPlugins: [
            remarkMath,
            remarkGfm,
            remarkMdx,
            remarkMention,
            remarkEmoji as any,
          ],
        }),
    },
    []
  );

  React.useEffect(() => {
    if (debouncedMarkdownValue !== initialMarkdown) {
      editor.tf.reset();
      editor.tf.setValue(
        editor.api.markdown.deserialize(debouncedMarkdownValue, {
          remarkPlugins: [
            remarkMath,
            remarkGfm,
            remarkMdx,
            remarkMention,
            remarkEmoji as any,
          ],
        })
      );
    }
  }, [debouncedMarkdownValue, editor]);

  return (
    <div className="grid h-full grid-cols-2 overflow-y-auto">
      <Plate
        onValueChange={() => {
          const value = markdownEditor.children
            .map((node: any) => markdownEditor.api.string(node))
            .join('\n');
          setMarkdownValue(value);
        }}
        editor={markdownEditor}
      >
        <EditorContainer>
          <Editor
            variant="none"
            className="bg-muted/50 p-2 font-mono text-sm"
          />
        </EditorContainer>
      </Plate>

      <Plate editor={editor}>
        <EditorContainer>
          <Editor variant="none" className="px-4 py-2" />
        </EditorContainer>
      </Plate>
    </div>
  );
}
Loading...
Files
components/demo.tsx
'use client';

import * as React from 'react';

import { Plate } from '@udecode/plate/react';

import { editorPlugins } from '@/components/editor/plugins/editor-plugins';
import { useCreateEditor } from '@/components/editor/use-create-editor';
import { Editor, EditorContainer } from '@/components/ui/editor';

import { DEMO_VALUES } from './values/demo-values';

export default function Demo({ id }: { id: string }) {
  const editor = useCreateEditor({
    plugins: [...editorPlugins],
    value: DEMO_VALUES[id],
  });

  return (
    <Plate editor={editor}>
      <EditorContainer variant="demo">
        <Editor />
      </EditorContainer>
    </Plate>
  );
}

Features

  • Markdown to Slate JSON: Convert Markdown strings to Plate's editable format (deserialize).
  • Slate JSON to Markdown: Convert Plate content back to Markdown strings (serialize).
  • Safe by Default: Handles Markdown conversion without dangerouslySetInnerHTML.
  • Customizable Rules: Define how specific Markdown syntax or custom Plate elements are converted using rules. Supports MDX.
  • Extensible: Utilize remark plugins via the remarkPlugins option.
  • Compliant: Supports CommonMark, with GFM (GitHub Flavored Markdown) available via remark-gfm.
  • Round-Trip Serialization: Preserves custom elements through MDX syntax during conversion cycles.

Why Use @udecode/plate-markdown?

While libraries like react-markdown render Markdown to React elements, @udecode/plate-markdown offers deeper integration with the Plate ecosystem:

  • Rich Text Editing: Enables advanced editing features by converting Markdown to Plate's structured format.
  • WYSIWYG Experience: Edit content in a rich text view and serialize it back to Markdown.
  • Custom Elements & Data: Handles complex custom Plate elements (mentions, embeds) by converting them to/from MDX.
  • Extensibility: Leverages Plate's plugin system and the unified/remark ecosystem for powerful customization.

If you only need to display Markdown as HTML without editing or custom elements, react-markdown might be sufficient. For a rich text editor with Markdown import/export and custom content, @udecode/plate-markdown is the integrated solution.

Installation

pnpm add @udecode/plate @udecode/plate-markdown

Usage

Configure the Plugin

Configuring MarkdownPlugin is recommended to enable Markdown paste handling and set default conversion rules.

lib/plate-editor.ts
import { createPlateEditor } from '@udecode/plate/react';
import { MarkdownPlugin, remarkMention, remarkMdx } from '@udecode/plate-markdown';
import remarkGfm from 'remark-gfm';
import remarkMath from 'remark-math';
 
const editor = createPlateEditor({
  plugins: [
    // ...other Plate plugins
    MarkdownPlugin.configure({
      options: {
        // Add remark plugins for syntax extensions (GFM, Math, MDX)
        remarkPlugins: [remarkMath, remarkGfm, remarkMdx, remarkMention],
        // Define custom rules if needed
        rules: {
          // date: { /* ... rule implementation ... */ },
        },
      },
    }),
  ],
});
 
// To disable Markdown paste handling:
const editorWithoutPaste = createPlateEditor({
  plugins: [
    // ...other Plate plugins
    MarkdownPlugin.configure(() => ({ parser: null })),
  ],
});

If you don't use MarkdownPlugin with configure, you can still use editor.api.markdown.deserialize and editor.api.markdown.serialize directly, but without plugin-configured default rules or paste handling.

Markdown to Plate (Deserialization)

Use editor.api.markdown.deserialize to convert a Markdown string into a Slate Value (an array of nodes). This is often used for the editor's initial content.

components/my-editor.tsx
import { createPlateEditor } from '@udecode/plate/react';
import { MarkdownPlugin } from '@udecode/plate-markdown';
// ... import other necessary Plate plugins for rendering elements
 
const markdownString = '# Hello, *Plate*!';
 
const editor = createPlateEditor({
  plugins: [
    // MarkdownPlugin must be included
    MarkdownPlugin,
    // ... other plugins needed to render the deserialized elements (e.g., HeadingPlugin, ItalicPlugin)
  ],
  // Use deserialize in the value factory for initial content
  value: (editor) => editor.getApi(MarkdownPlugin).markdown.deserialize(markdownString),
});

Plugin Requirements

Ensure all Plate plugins required to render the deserialized Markdown (e.g., HeadingPlugin for #, TablePlugin for tables) are included in your editor's plugins array.

Plate to Markdown (Serialization)

Use editor.api.markdown.serialize to convert the current editor content (or a specific array of nodes) into a Markdown string.

Serializing Current Editor Content:

// Assuming `editor` is your Plate editor instance with content
const markdownOutput = editor.api.markdown.serialize();
console.info(markdownOutput);

Serializing Specific Nodes:

const specificNodes = [
  { type: 'p', children: [{ text: 'Serialize just this paragraph.' }] },
  { type: 'h1', children: [{ text: 'And this heading.' }] }
];
 
// Assuming `editor` is your Plate editor instance
const partialMarkdownOutput = editor.api.markdown.serialize({
  value: specificNodes,
});
console.info(partialMarkdownOutput);

Round-Trip Serialization with Custom Elements (MDX)

A key feature is handling custom Plate elements that lack standard Markdown representation (e.g., underline, mentions). @udecode/plate-markdown converts these to MDX elements during serialization and parses them back during deserialization.

Example: Handling a custom date element.

Slate Node Structure:

{
  type: 'p',
  children: [
    { text: 'Today is ' },
    { type: 'date', date: '2025-03-31', children: [{ text: '' }] } // Leaf elements need a text child
  ],
}

Plugin Configuration with rules:

lib/plate-editor.ts
import type { MdMdxJsxTextElement } from '@udecode/plate-markdown';
import { MarkdownPlugin, remarkMdx } from '@udecode/plate-markdown';
// ... other imports
 
MarkdownPlugin.configure({
  options: {
    rules: {
      // Key matches:
      // 1. Plate element's plugin 'key' or 'type'.
      // 2. mdast node type.
      // 3. MDX tag name.
      date: {
        // Markdown -> Slate
        deserialize(mdastNode: MdMdxJsxTextElement, deco, options) {
          const dateValue = (mdastNode.children?.[0] as any)?.value || '';
          return {
            type: 'date', // Your Plate element type
            date: dateValue,
            children: [{ text: '' }], // Valid Slate structure
          };
        },
        // Slate -> Markdown (MDX)
        serialize: (slateNode): MdMdxJsxTextElement => {
          return {
            type: 'mdxJsxTextElement',
            name: 'date', // MDX tag name
            attributes: [], // Optional: [{ type: 'mdxJsxAttribute', name: 'date', value: slateNode.date }]
            children: [{ type: 'text', value: slateNode.date || '1999-01-01' }],
          };
        },
      },
      // ... rules for other custom elements
    },
    remarkPlugins: [remarkMdx /*, ... other remark plugins like remarkGfm */],
  },
});

Conversion Process:

  1. Serialization (Slate → Markdown): The Plate date node becomes <date>2025-03-31</date>.
  2. Deserialization (Markdown → Slate): The MDX tag <date>2025-03-31</date> converts back to the Plate date node.

API Reference

MarkdownPlugin

The core plugin configuration object. Use MarkdownPlugin.configure({ options: {} }) to set global options for Markdown processing.

Options

Collapse all

    Whitelist specific node types (Slate types and Markdown AST types like strong). Cannot be used with disallowedNodes. If set, only listed types are processed. Default: null (all allowed).

    Blacklist specific node types. Cannot be used with allowedNodes. Listed types are filtered out. Default: null.

    Fine-grained node filtering with custom functions, applied after allowedNodes/disallowedNodes.

    • deserialize?: (mdastNode: any) => boolean: Filter for Markdown → Slate. Return true to keep.
    • serialize?: (slateNode: any) => boolean: Filter for Slate → Markdown. Return true to keep. Default: null.

    Custom conversion rules between Markdown AST and Slate elements. See Round-Trip Serialization and Customizing Conversion Rules. For marks/leaves, ensure the rule object has mark: true. Default: null (uses internal defaultRules).

    Array of remark plugins (e.g., remark-gfm, remark-math, remark-mdx). Operates on Markdown AST (mdast). Default: [].

Attributes

Collapse all

    Configuration for pasted content. Set to null to disable Markdown paste handling. Default enables pasting text/plain as Markdown. See PlatePlugin API > parser.


editor.api.markdown.deserialize

Converts a Markdown string into a Slate Value (Descendant[]).

Parameters

Collapse all

    The Markdown string to deserialize.

    Options for this call, overriding plugin defaults.

OptionsDeserializeMdOptions

Collapse all

    Override plugin allowedNodes.

    Override plugin disallowedNodes.

    Override plugin allowNode.

    Adds _memo property with raw Markdown to top-level blocks for memoization (e.g., with PlateStatic). Default: false.

    Override plugin rules.

    Options for the underlying Markdown block parser (parseMarkdownBlocks). See below.

    Override plugin remarkPlugins.

    If true, single line breaks (\\n) in paragraphs become paragraph breaks. Default: false.

ReturnsDescendant[]

    An array of Slate nodes.


editor.api.markdown.serialize

Converts a Slate Value (Descendant[]) into a Markdown string.

Parameters

Collapse all

    Options for this call, overriding plugin defaults.

OptionsSerializeMdOptions

Collapse all

    Slate nodes to serialize. Defaults to editor.children.

    Override plugin allowedNodes.

    Override plugin disallowedNodes.

    Override plugin allowNode.

    Override plugin rules.

    Override plugin remarkPlugins (affects stringification).

Returnsstring

    A Markdown string.


parseMarkdownBlocks

Utility to parse a Markdown string into block-level tokens (used by deserialize, useful with memoize).

Parameters

Collapse all

    The Markdown string.

    Parsing options.

OptionsParseMarkdownBlocksOptions

Collapse all

    Marked token types (e.g., 'space') to exclude. Default: ['space'].

    Trim trailing whitespace from input. Default: true.

ReturnsToken[]

    Array of marked Token objects with raw Markdown.

Examples

Using a Remark Plugin (GFM)

Add support for GitHub Flavored Markdown (tables, strikethrough, task lists, autolinks).

Plugin Configuration:

lib/plate-editor.ts
import { createPlateEditor } from '@udecode/plate/react';
import { MarkdownPlugin } from '@udecode/plate-markdown';
import remarkGfm from 'remark-gfm';
// Import Plate plugins for GFM elements
import { TablePlugin } from '@udecode/plate-table/react';
import { TodoListPlugin } from '@udecode/plate-list/react'; // Ensure this is the correct List plugin for tasks
import { StrikethroughPlugin } from '@udecode/plate-basic-marks/react';
import { LinkPlugin } from '@udecode/plate-link/react';
 
const editor = createPlateEditor({
  plugins: [
    // ...other plugins
    TablePlugin,
    TodoListPlugin, // Or your specific task list plugin
    StrikethroughPlugin,
    LinkPlugin,
    MarkdownPlugin.configure({
      options: {
        remarkPlugins: [remarkGfm],
      },
    }),
  ],
});

Usage:

const markdown = `
A table:
 
| a | b |
| - | - |
 
~~Strikethrough~~
 
- [x] Task list item
 
Visit https://platejs.org
`;
 
// Assuming `editor` is your configured Plate editor instance
const slateValue = editor.api.markdown.deserialize(markdown);
// editor.tf.setValue(slateValue); // To set editor content
 
const markdownOutput = editor.api.markdown.serialize();
// markdownOutput will contain GFM syntax

Customizing Rendering (Syntax Highlighting)

This example shows two approaches: customizing the rendering component (common for UI changes) and customizing the conversion rule (advanced, for changing Slate structure).

Background:

  • @udecode/plate-markdown converts Markdown fenced code blocks (e.g., ```js ... ```) to Slate code_block elements with code_line children.
  • The Plate CodeBlockElement (often from @udecode/plate-code-block/react) renders this structure.
  • Syntax highlighting typically occurs within CodeBlockElement using a library like lowlight (via CodeBlockPlugin). See Code Block Plugin for details.

Approach 1: Customizing Rendering Component (Recommended for UI)

To change how code blocks appear, customize the component for the code_block plugin key.

components/my-editor.tsx
import { createPlateEditor } from '@udecode/plate/react';
import { CodeBlockPlugin } from '@udecode/plate-code-block/react'; // Main plugin
import { MarkdownPlugin } from '@udecode/plate-markdown';
import { MyCustomCodeBlockElement } from './my-custom-code-block'; // Your custom component
 
const editor = createPlateEditor({
  plugins: [
    CodeBlockPlugin, // Base plugin for structure/logic
    MarkdownPlugin,  // For Markdown conversion
    // ... other plugins
  ],
  components: {
    [CodeBlockPlugin.key]: MyCustomCodeBlockElement,
    // You might also need to customize components for CodeLinePlugin.key and CodeSyntaxPlugin.key
    // depending on your MyCustomCodeBlockElement implementation.
  },
});
 
// MyCustomCodeBlockElement.tsx would then implement the desired rendering
// (e.g., using react-syntax-highlighter), consuming props from PlateElement.

Refer to the Code Block Plugin documentation for complete examples.

Approach 2: Customizing Conversion Rule (Advanced - Changing Slate Structure)

To fundamentally alter the Slate JSON for code blocks (e.g., storing code as a single string prop), override the deserialize rule.

lib/plate-editor.ts
import { MarkdownPlugin } from '@udecode/plate-markdown';
import { CodeBlockPlugin } from '@udecode/plate-code-block/react';
 
MarkdownPlugin.configure({
  options: {
    rules: {
      // Override deserialization for mdast 'code' type
      code: {
        deserialize: (mdastNode, deco, options) => {
          return {
            type: CodeBlockPlugin.key, // Use Plate's type
            lang: mdastNode.lang ?? undefined,
            rawCode: mdastNode.value || '', // Store raw code directly
            children: [{ text: '' }], // Slate Element needs a dummy text child
          };
        },
      },
      // A custom `serialize` rule for `code_block` would also be needed
      // to convert `rawCode` back to an mdast 'code' node.
      [CodeBlockPlugin.key]: { // Assuming CodeBlockPlugin.key is 'code_block'
          serialize: (slateNode, options) => {
             return { // mdast 'code' node
                type: 'code',
                lang: slateNode.lang,
                value: slateNode.rawCode,
             };
          },
      },
    },
    // remarkPlugins: [...]
  },
});
 
// Your custom rendering component (MyCustomCodeBlockElement) would then
// need to read the code from the `rawCode` property.

Choose based on whether you're changing UI (Approach 1) or data structure (Approach 2).

Using Remark Plugins for Math (remark-math)

Enable TeX math syntax ($inline$, $$block$$).

Plugin Configuration:

lib/plate-editor.ts
import { createPlateEditor } from '@udecode/plate/react';
import { MarkdownPlugin } from '@udecode/plate-markdown';
import remarkMath from 'remark-math';
// Import Plate math plugins for rendering
import { MathPlugin } from '@udecode/plate-math/react'; // Main Math plugin
 
const editor = createPlateEditor({
  plugins: [
    // ...other plugins
    MathPlugin, // Renders block and inline equations
    MarkdownPlugin.configure({
      options: {
        remarkPlugins: [remarkMath],
        // Default rules handle 'math' and 'inlineMath' mdast types from remark-math,
        // converting them to Plate's 'equation' and 'inline_equation' types.
      },
    }),
  ],
});

Usage:

const markdown = `
Inline math: $E=mc^2$
 
Block math:
$$
\\int_a^b f(x) dx = F(b) - F(a)
$$
`;
 
// Assuming `editor` is your configured Plate editor instance
const slateValue = editor.api.markdown.deserialize(markdown);
// slateValue will contain 'inline_equation' and 'equation' nodes.
 
const markdownOutput = editor.api.markdown.serialize({ value: slateValue });
// markdownOutput will contain $...$ and $$...$$ syntax.

Remark Plugins

@udecode/plate-markdown leverages the unified / remark ecosystem. Extend its capabilities by adding remark plugins via the remarkPlugins option in MarkdownPlugin.configure. These plugins operate on the mdast (Markdown Abstract Syntax Tree).

Finding Plugins:

Common Uses:

  • Syntax Extensions: remark-gfm (tables, etc.), remark-math (TeX), remark-frontmatter, remark-mdx.
  • Linting/Formatting: remark-lint (often separate tooling).
  • Custom Transformations: Custom plugins to modify mdast.

Remark vs. Rehype

Plate components (e.g., TableElement, CodeBlockElement) render Slate JSON. remarkPlugins modify the Markdown AST. Unlike some renderers, rehypePlugins (for HTML AST) are generally not needed for Plate rendering, but can be used within the remark pipeline for complex HTML transformations (e.g., with rehype-raw).

Syntax Support

@udecode/plate-markdown uses remark-parse, adhering to CommonMark. Enable GFM or other syntaxes via remarkPlugins.

Architecture Overview

@udecode/plate-markdown bridges Markdown strings and Plate's editor format using the unified/remark ecosystem.

                                             @udecode/plate-markdown
          +--------------------------------------------------------------------------------------------+
          |                                                                                            |
          |  +-----------+        +----------------+        +---------------+      +-----------+       |
          |  |           |        |                |        |               |      |           |       |
 markdown-+->+ remark    +-mdast->+ remark plugins +-mdast->+ mdast-to-slate+----->+   nodes   +-plate-+->react elements 
          |  |           |        |                |        |               |      |           |       |
          |  +-----------+        +----------------+        +---------------+      +-----------+       |
          |       ^                                                                      |             |
          |       |                                                                      v             |
          |  +-----------+        +----------------+        +---------------+      +-----------+       |
          |  |           |        |                |        |               |      |           |       |
          |  | stringify |<-mdast-+ remark plugins |<-mdast-+ slate-to-mdast+<-----+ serialize |       |
          |  |           |        |                |        |               |      |           |       |
          |  +-----------+        +----------------+        +---------------+      +-----------+       |
          |                                                                                            |
          +--------------------------------------------------------------------------------------------+

Key Steps:

  1. Parse (Deserialization):
    • Markdown string → remark-parse → mdast.
    • remarkPlugins transform mdast (e.g., remark-gfm).
    • mdast-to-slate converts mdast to Plate nodes using rules.
    • Plate renders nodes via its component system.
  2. Stringify (Serialization):
    • Plate nodes → slate-to-mdast (using rules) → mdast.
    • remarkPlugins transform mdast.
    • remark-stringify converts mdast to Markdown string.

Comparison with react-markdown

  • Direct Node Rendering: Plate directly renders its nodes via components, unlike react-markdown which often uses rehype to convert Markdown to HTML, then to React elements.
  • Bidirectional: Plate's Markdown processor is fully bidirectional.
  • Rich Text Integration: Nodes are integrated with Plate's editing capabilities.
  • Plugin System: Components are managed via Plate's plugin system.

Migrating from react-markdown

Migrating involves mapping react-markdown concepts to Plate's architecture.

Key Differences:

  1. Rendering Pipeline: react-markdown (MD → mdast → hast → React) vs. @udecode/plate-markdown (MD ↔ mdast ↔ Slate JSON; Plate components render Slate JSON).
  2. Component Customization:
    • react-markdown: components prop replaces HTML tag renderers.
    • Plate:
      • MarkdownPlugin rules: Customize mdast ↔ Slate JSON conversion.
      • createPlateEditor components: Customize React components for Plate node types. See Appendix C.
  3. Plugin Ecosystem: @udecode/plate-markdown primarily uses remarkPlugins. rehypePlugins are less common.

Mapping Options:

react-markdown Prop@udecode/plate-markdown Equivalent/ConceptNotes
children (string)Pass to editor.api.markdown.deserialize(string)Input for deserialization; often in createPlateEditor value option.
remarkPluginsMarkdownPlugin.configure({ options: { remarkPlugins: [...] }})Direct mapping; operates on mdast.
rehypePluginsUsually not needed. Use remarkPlugins for syntax.Plate components handle rendering. For raw HTML, use rehype-raw via remarkPlugins.
components={{ h1: MyH1 }}createPlateEditor({ components: { h1: MyH1 } })Configures Plate rendering component. Key depends on HeadingPlugin config.
components={{ code: MyCode }}1. Conversion: MarkdownPlugin > rules > code. 2. Rendering: components: { [CodeBlockPlugin.key]: MyCode }rules for mdast (code) to Slate (code_block). components for Slate rendering.
allowedElementsMarkdownPlugin.configure({ options: { allowedNodes: [...] }})Filters nodes during conversion (mdast/Slate types).
disallowedElementsMarkdownPlugin.configure({ options: { disallowedNodes: [...] }})Filters nodes during conversion.
unwrapDisallowedNo direct equivalent. Filtering removes nodes.Custom rules could implement unwrapping.
skipHtmlDefault behavior strips most HTML.Use rehype-raw via remarkPlugins for HTML processing.
urlTransformCustomize via rules for link (deserialize) or plugin type (serialize).Handle URL transformations in conversion rules.
allowElementMarkdownPlugin.configure({ options: { allowNode: { ... } } })Function-based filtering during conversion.

Appendix A: HTML in Markdown

By default, @udecode/plate-markdown does not process raw HTML tags for security. Standard Markdown generating HTML (e.g., *emphasis*<em>) is handled.

To process raw HTML in a trusted environment:

  1. Include remark-mdx: Add to remarkPlugins.
  2. Use rehype-raw: Add rehype-raw to remarkPlugins.
  3. Configure Rules: May need rules for parsed HTML hast nodes to Slate structures.
lib/plate-editor.ts
import { MarkdownPlugin, remarkMdx } from '@udecode/plate-markdown';
import rehypeRaw from 'rehype-raw'; // May require VFile, ensure compatibility
// import { VFile } from 'vfile'; // If needed by rehype-raw setup
 
MarkdownPlugin.configure({
  options: {
    remarkPlugins: [
      remarkMdx,
      // Using rehype plugins within remark pipeline can be complex.
      [rehypeRaw, { /* options, e.g., pass vfile */ }],
    ],
    rules: {
      // Example: Rule for HTML tags parsed by rehype-raw.
      // mdastNode structure depends on rehype-raw output.
      element: { // Generic rule for 'element' nodes from rehype-raw
        deserialize: (mdastNode, deco, options) => {
          // Simplified: Needs proper handling based on mdastNode.tagName and attributes.
          // You'll likely need specific rules per HTML tag.
          if (mdastNode.tagName === 'div') {
            return {
              type: 'html_div', // Example: Map to a custom 'html_div' Plate element
              children: convertChildrenDeserialize(mdastNode.children, deco, options),
            };
          }
          // Fallback or handle other tags
          return convertChildrenDeserialize(mdastNode.children, deco, options);
        },
      },
      // Add serialization rules if outputting raw HTML from Slate.
    },
  },
});

Security Warning

Enabling raw HTML rendering increases XSS risk if the Markdown source isn't trusted. Use rehype-sanitize after rehype-raw to whitelist HTML elements/attributes.

Appendix B: Customizing Conversion Rules (rules)

The rules option in MarkdownPlugin.configure offers fine-grained control over mdast ↔ Slate JSON conversion. Keys in the rules object match node types.

  • Deserialization (Markdown → Slate): Keys are mdast node types (e.g., paragraph, heading, strong, link, MDX types like mdxJsxTextElement). The deserialize function takes (mdastNode, deco, options) and returns a Slate Descendant or Descendant[].
  • Serialization (Slate → Markdown): Keys are Plate element/text types (e.g., p, h1, a, code_block, bold). The serialize function takes (slateNode, options) and returns an mdast node.

Example: Overriding Link Deserialization

lib/plate-editor.ts
MarkdownPlugin.configure({
  options: {
    rules: {
      // Rule for mdast 'link' type
      link: {
        deserialize: (mdastNode, deco, options) => {
          // Default creates { type: 'a', url: ..., children: [...] }
          // Add a custom property:
          return {
            type: 'a', // Plate link element type
            url: mdastNode.url,
            title: mdastNode.title,
            customProp: 'added-during-deserialize',
            children: convertChildrenDeserialize(mdastNode.children, deco, options),
          };
        },
      },
      // Rule for Plate 'a' type (if serialization needs override for customProp)
      a: { // Assuming 'a' is the Plate type for links
         serialize: (slateNode, options) => {
           // Default creates mdast 'link'
           // Handle customProp if needed in MDX attributes or similar
           return {
             type: 'link', // mdast type
             url: slateNode.url,
             title: slateNode.title,
             // customProp: slateNode.customProp, // MDX attribute?
             children: convertNodesSerialize(slateNode.children, options),
           };
         },
      },
    },
    // ... remarkPlugins ...
  },
});

Default Rules Summary: Refer to defaultRules.ts for the complete list. Key conversions include:

Markdown (mdast)Plate TypeNotes
paragraphp
heading (depth)h1 - h6Based on depth.
blockquoteblockquote
list (ordered)ol / p*ol/li/lic or p with list indent props.
list (unordered)ul / p*ul/li/lic or p with list indent props.
code (fenced)code_blockContains code_line children.
inlineCodecode (mark)Applied to text.
strongbold (mark)Applied to text.
emphasisitalic (mark)Applied to text.
deletestrikethrough (mark)Applied to text.
linka
imageimgWraps in paragraph during serialization.
thematicBreakhr
tabletableContains tr.
math (block)equationRequires remark-math.
inlineMathinline_equationRequires remark-math.
mdxJsxFlowElementCustomRequires remark-mdx and custom rules.
mdxJsxTextElementCustomRequires remark-mdx and custom rules.

* List conversion depends on IndentListPlugin detection.


Default MDX Conversions (with remark-mdx):

MDX (mdast)Plate TypeNotes
<del>...</del>strikethrough (mark)Alt for ~~strikethrough~~
<sub>...</sub>subscript (mark)H2O
<sup>...</sup>superscript (mark)E=mc2
<u>...</u>underline (mark)Underlined
<mark>...</mark>highlight (mark)Highlighted
<span style="font-family: ...">fontFamily (mark)
<span style="font-size: ...">fontSize (mark)
<span style="font-weight: ...">fontWeight (mark)
<span style="color: ...">color (mark)
<span style="background-color: ...">backgroundColor (mark)
<date>...</date>dateCustom Date element
@mentionmentionCustom Mention element
<file name="..." />fileCustom File element
<audio src="..." />audioCustom Audio element
<video src="..." />videoCustom Video element
<toc />tocTable of Contents
<callout>...</callout>calloutCallout block
<column_group>...column_groupMulti-column layout
<column>...columnSingle column (part of column_group)

Appendix C: Components for Rendering

While rules handle MD ↔ Slate conversion, Plate uses React components to render Slate nodes. Configure these in createPlateEditor via the components option. This is analogous to react-markdown's components prop but within Plate's ecosystem.

Keys in components usually match Plate plugin keys (e.g., ParagraphPlugin.key, CodeBlockPlugin.key) or default Slate types (p, h1).

Example:

components/my-editor.tsx
import { createPlateEditor, PlateLeaf, withProps } from '@udecode/plate/react';
import { BoldPlugin } from '@udecode/plate-basic-marks/react';
import { CodeBlockPlugin } from '@udecode/plate-code-block/react';
import { ParagraphElement } from '@/components/ui/paragraph-element'; // Example UI component
import { CodeBlockElement } from '@/components/ui/code-block-element'; // Example UI component
// For marks, you might use PlateLeaf or a custom leaf component
// import { CodeLeaf } from '@/components/ui/code-leaf';
 
const editor = createPlateEditor({
  plugins: [BoldPlugin, CodeBlockPlugin /* ... */],
  components: {
    [ParagraphPlugin.key]: ParagraphElement,
    [CodeBlockPlugin.key]: CodeBlockElement,
    [BoldPlugin.key]: withProps(PlateLeaf, { as: 'strong' }),
    // For code syntax highlighting within code blocks:
    // [CodeBlockPlugin.keys.syntax]: CodeLeaf, // If CodeBlockPlugin provides such a key
  },
});

Refer to Plugin Components for more on creating/registering components.

Appendix D: PlateMarkdown Component (Read-Only Display)

For a react-markdown-like component for read-only display:

components/plate-markdown.tsx
import React, { useEffect } from 'react';
import { Plate, PlateContent, usePlateEditor } from '@udecode/plate/react';
import { MarkdownPlugin } from '@udecode/plate-markdown';
// Import necessary Plate plugins for common Markdown features
import { HeadingPlugin } from '@udecode/plate-heading/react';
// ... include other plugins like BlockquotePlugin, CodeBlockPlugin, ListPlugin, etc.
// ... and mark plugins like BoldPlugin, ItalicPlugin, etc.
 
export interface PlateMarkdownProps {
  children: string; // Markdown content
  remarkPlugins?: any[];
  components?: Record<string, React.ComponentType<any>>; // Plate component overrides
  className?: string;
}
 
export function PlateMarkdown({
  children,
  remarkPlugins = [],
  components = {},
  className,
}: PlateMarkdownProps) {
  const editor = usePlateEditor({
    plugins: [ // Include all plugins needed to render your Markdown
      HeadingPlugin, /* ... other plugins ... */
      MarkdownPlugin.configure({ options: { remarkPlugins } }),
    ],
    components, // Pass through component overrides
  });
 
  useEffect(() => {
    editor.tf.reset(); // Clear previous content
    editor.tf.setValue(
      editor.getApi(MarkdownPlugin).markdown.deserialize(children)
    );
  }, [children, editor, remarkPlugins]); // Re-deserialize if markdown or plugins change
 
  return (
    <Plate editor={editor}>
      <PlateContent readOnly className={className} />
    </Plate>
  );
}
 
// Usage Example:
// const markdownString = "# Hello\nThis is *Markdown*.";
// <PlateMarkdown className="prose dark:prose-invert">
//   {markdownString}
// </PlateMarkdown>

Initial Value

This PlateMarkdown component provides a read-only view. For full editing, see the Installation guides.

Security Considerations

@udecode/plate-markdown prioritizes safety by converting Markdown to a structured Slate format, avoiding direct HTML rendering. However, security depends on:

  • Custom rules: Ensure deserialize rules don't introduce unsafe data.
  • remarkPlugins: Vet third-party remark plugins for potential security risks.
  • Raw HTML Processing: If rehype-raw is used, always sanitize with rehype-sanitize if the source is untrusted.
  • Plugin Responsibility: URL validation in LinkPlugin (isUrl) or MediaEmbedPlugin (parseMediaUrl) is crucial.

Recommendation: Treat untrusted Markdown input cautiously. Sanitize if allowing complex features or raw HTML.