{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "markdown-docs",
  "title": "Markdown",
  "description": "Convert Plate content to Markdown and vice-versa.",
  "files": [
    {
      "path": "../../content/docs/(plugins)/(serializing)/markdown.mdx",
      "content": "---\ntitle: Markdown\ndescription: Convert Plate content to Markdown and vice-versa.\ntoc: true\n---\n\nThe `@platejs/markdown` package provides robust, two-way conversion between Markdown and Plate's content structure.\n\n<ComponentPreview name=\"markdown-to-slate-demo\" />\n\n<ComponentPreview name=\"markdown-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- **Markdown to Plate JSON:** Convert Markdown strings to Plate's editable format (`deserialize`).\n- **Plate JSON to Markdown:** Convert Plate content back to Markdown strings (`serialize`).\n- **Safe by Default:** Handles Markdown conversion without `dangerouslySetInnerHTML`.\n- **Customizable Rules:** Define how specific Markdown syntax or custom Plate elements are converted using `rules`. Supports MDX.\n- **Extensible:** Utilize [remark plugins](https://github.com/remarkjs/remark/blob/main/doc/plugins.md#list-of-plugins) via the `remarkPlugins` option.\n- **Compliant:** Supports CommonMark, with GFM (GitHub Flavored Markdown) available via [`remark-gfm`](https://github.com/remarkjs/remark-gfm).\n- **Round-Trip Serialization:** Preserves custom elements through MDX syntax during conversion cycles.\n\n</PackageInfo>\n\n## Why Use Plate Markdown?\n\nWhile libraries like `react-markdown` render Markdown to React elements, `@platejs/markdown` offers deeper integration with the Plate ecosystem:\n\n- **Rich Text Editing:** Enables advanced editing features by converting Markdown to Plate's structured format.\n- **WYSIWYG Experience:** Edit content in a rich text view and serialize it back to Markdown.\n- **Custom Elements & Data:** Handles complex custom Plate elements (mentions, embeds) by converting them to/from MDX.\n- **Extensibility:** Leverages Plate's plugin system and the unified/remark ecosystem for powerful customization.\n\n<Callout type=\"note\">\n  If you only need to display Markdown as HTML without editing or custom\n  elements, `react-markdown` might be sufficient. For a rich text editor with\n  Markdown import/export and custom content, `@platejs/markdown` is the\n  integrated solution.\n</Callout>\n\n## Kit Usage\n\n<Steps>\n\n### Installation\n\nThe fastest way to add Markdown functionality is with the `MarkdownKit`, which includes pre-configured `MarkdownPlugin` with essential remark plugins for [Plate UI](/docs/installation/plate-ui) compatibility.\n\n<ComponentSource name=\"markdown-kit\" />\n\n### Add Kit\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\nimport { MarkdownKit } from '@/components/editor/plugins/markdown-kit';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    ...MarkdownKit,\n  ],\n});\n```\n\n</Steps>\n\n## Manual Usage\n\n<Steps>\n\n### Installation\n\n```bash\nnpm install platejs @platejs/markdown\n```\n\n### Add Plugin\n\n```tsx\nimport { MarkdownPlugin } from '@platejs/markdown';\nimport { createPlateEditor } from 'platejs/react';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...otherPlugins,\n    MarkdownPlugin,\n  ],\n});\n```\n\n### Configure Plugin\n\nConfiguring `MarkdownPlugin` is recommended to enable Markdown paste handling and set default conversion `rules`.\n\n```tsx title=\"lib/plate-editor.ts\"\nimport { createPlateEditor } from 'platejs/react';\nimport {\n  MarkdownPlugin,\n  remarkMention,\n  remarkMdx,\n} from '@platejs/markdown';\nimport remarkEmoji from 'remark-emoji';\nimport remarkGfm from 'remark-gfm';\nimport remarkMath from 'remark-math';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...other Plate plugins\n    MarkdownPlugin.configure({\n      options: {\n        // Add remark plugins for syntax extensions (GFM, emoji shortcodes, Math, MDX)\n        remarkPlugins: [remarkMath, remarkGfm, remarkEmoji, remarkMdx, remarkMention],\n        // Define custom rules if needed\n        rules: {\n          // date: { /* ... rule implementation ... */ },\n        },\n      },\n    }),\n  ],\n});\n\n// To disable Markdown paste handling:\nconst editorWithoutPaste = createPlateEditor({\n  plugins: [\n    // ...other Plate plugins\n    MarkdownPlugin.configure(() => ({ parser: null })),\n  ],\n});\n```\n\n<Callout type=\"info\">\n  If you don't use `MarkdownPlugin` with `configure`, you can still use\n  `editor.api.markdown.deserialize` and `editor.api.markdown.serialize`\n  directly, but without plugin-configured default rules or paste handling.\n</Callout>\n\n### Markdown to Plate (Deserialization)\n\nUse `editor.api.markdown.deserialize` to convert a Markdown string into a Plate `Value` (an array of nodes). This is often used for the editor's initial content.\n\n```tsx title=\"components/my-editor.tsx\"\nimport { createPlateEditor } from 'platejs/react';\nimport { MarkdownPlugin } from '@platejs/markdown';\n// ... import other necessary Plate plugins for rendering elements\n\nconst markdownString = '# Hello, *Plate*!';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // MarkdownPlugin must be included\n    MarkdownPlugin,\n    // ... other plugins needed to render the deserialized elements (e.g., HeadingPlugin, ItalicPlugin)\n  ],\n  // Use deserialize in the value factory for initial content\n  value: (editor) =>\n    editor.getApi(MarkdownPlugin).markdown.deserialize(markdownString),\n});\n```\n\n<Callout type=\"warning\" title=\"Plugin Requirements\">\n  Ensure all Plate plugins required to render the deserialized Markdown (e.g.,\n  `HeadingPlugin` for `#`, `TablePlugin` for tables) are included in your\n  editor's `plugins` array.\n</Callout>\n\n### Plate to Markdown (Serialization)\n\nUse `editor.api.markdown.serialize` to convert the current editor content (or a specific array of nodes) into a Markdown string.\n\n**Serializing Current Editor Content:**\n\n```tsx\n// Assuming `editor` is your Plate editor instance with content\nconst markdownOutput = editor.api.markdown.serialize();\nconsole.info(markdownOutput);\n```\n\n**Serializing Specific Nodes:**\n\n```tsx\nconst specificNodes = [\n  { type: 'p', children: [{ text: 'Serialize just this paragraph.' }] },\n  { type: 'h1', children: [{ text: 'And this heading.' }] },\n];\n\n// Assuming `editor` is your Plate editor instance\nconst partialMarkdownOutput = editor.api.markdown.serialize({\n  value: specificNodes,\n});\nconsole.info(partialMarkdownOutput);\n```\n\n### Round-Trip Serialization with Custom Elements (MDX)\n\nA key feature is handling custom Plate elements that lack standard Markdown representation (e.g., underline, mentions). `@platejs/markdown` converts these to [MDX][github-mdx] elements during serialization and parses them back during deserialization.\n\n**Example:** Handling a custom `date` element.\n\n**Plate Node Structure:**\n\n```ts\n{\n  type: 'p',\n  children: [\n    { text: 'Today is ' },\n    { type: 'date', date: '2025-03-31', children: [{ text: '' }] } // Leaf elements need a text child\n  ],\n}\n```\n\n**Plugin Configuration with `rules`:**\n\n```tsx title=\"lib/plate-editor.ts\"\nimport type { MdMdxJsxTextElement } from '@platejs/markdown';\nimport { MarkdownPlugin, remarkMdx } from '@platejs/markdown';\n// ... other imports\n\nMarkdownPlugin.configure({\n  options: {\n    rules: {\n      // Key matches:\n      // 1. Plate element's plugin 'key' or 'type'.\n      // 2. mdast node type.\n      // 3. MDX tag name.\n      date: {\n        // Markdown -> Plate\n        deserialize(mdastNode: MdMdxJsxTextElement, deco, options) {\n          const dateValue = (mdastNode.children?.[0] as any)?.value || '';\n          return {\n            type: 'date', // Your Plate element type\n            date: dateValue,\n            children: [{ text: '' }], // Valid Plate structure\n          };\n        },\n        // Plate -> Markdown (MDX)\n        serialize: (slateNode): MdMdxJsxTextElement => {\n          return {\n            type: 'mdxJsxTextElement',\n            name: 'date', // MDX tag name\n            attributes: [], // Optional: [{ type: 'mdxJsxAttribute', name: 'date', value: slateNode.date }]\n            children: [{ type: 'text', value: slateNode.date || '1999-01-01' }],\n          };\n        },\n      },\n      // ... rules for other custom elements\n    },\n    remarkPlugins: [remarkMdx /*, ... other remark plugins like remarkGfm */],\n  },\n});\n```\n\n**Conversion Process:**\n\n1.  **Serialization (Plate → Markdown):** The Plate `date` node writes as `<date value=\"2025-03-31\" />`.\n2.  **Deserialization (Markdown → Plate):** Both `<date value=\"2025-03-31\" />` and `<date>2025-03-31</date>` convert back to the Plate `date` node.\n\n</Steps>\n\n## API Reference\n\n### `MarkdownPlugin`\n\nThe core plugin configuration object. Use `MarkdownPlugin.configure({ options: {} })` to set global options for Markdown processing.\n\n<API name=\"MarkdownPlugin\">\n  <APIOptions>\n    <APIItem name=\"allowedNodes\" type=\"PlateType | null\">\n      Whitelist specific node types (Plate types and Markdown AST types like\n      `strong`). Cannot be used with `disallowedNodes`. If set, only listed\n      types are processed. Default: `null` (all allowed).\n    </APIItem>\n    <APIItem name=\"disallowedNodes\" type=\"PlateType | null\">\n      Blacklist specific node types. Cannot be used with `allowedNodes`. Listed\n      types are filtered out. Default: `null`.\n    </APIItem>\n    <APIItem name=\"allowNode\" type=\"AllowNodeConfig\">\n      Fine-grained node filtering with custom functions, applied *after*\n      `allowedNodes`/`disallowedNodes`. - `deserialize?: (mdastNode: any) =>\n      boolean`: Filter for Markdown → Plate. Return `true` to keep. -\n      `serialize?: (slateNode: any) => boolean`: Filter for Plate → Markdown.\n      Return `true` to keep. Default: `null`.\n    </APIItem>\n    <APIItem name=\"rules\" type=\"MdRules | null\">\n      Custom conversion rules between Markdown AST and Plate elements. See\n      [Round-Trip\n      Serialization](#round-trip-serialization-with-custom-elements-mdx) and\n      [Customizing Conversion Rules](#appendix-b-customizing-conversion-rules).\n      For marks/leaves, ensure the rule object has `mark: true`. Default: `null`\n      (uses internal `defaultRules`).\n    </APIItem>\n    <APIItem name=\"remarkPlugins\" type=\"Plugin[]\">\n      Array of [remark\n      plugins](https://github.com/remarkjs/remark/blob/main/doc/plugins.md#list-of-plugins)\n      (e.g., `remark-gfm`, `remark-math`, `remark-mdx`). Operates on Markdown\n      AST (`mdast`). Default: `[]`.\n    </APIItem>\n  </APIOptions>\n  <APIAttributes>\n    <APIItem name=\"parser\" type=\"Parser | null\">\n      Configuration for pasted content. Set to `null` to disable Markdown paste\n      handling. Default enables pasting `text/plain` as Markdown. See\n      [PlatePlugin API > parser](/docs/api/core/plate-plugin#parser).\n    </APIItem>\n  </APIAttributes>\n</API>\n\n---\n\n### `api.markdown.deserialize`\n\nConverts a Markdown string into a Plate `Value` (`Descendant[]`).\n\n<API name=\"deserialize\">\n  <APIParameters>\n    <APIItem name=\"markdown\" type=\"string\">\n      The Markdown string to deserialize.\n    </APIItem>\n    <APIItem name=\"options\" type=\"DeserializeMdOptions\" optional>\n      Options for this call, overriding plugin defaults.\n    </APIItem>\n  </APIParameters>\n  <APIOptions type=\"DeserializeMdOptions\">\n    <APIItem name=\"allowedNodes\" type=\"PlateType\" optional>\n      Override plugin `allowedNodes`.\n    </APIItem>\n    <APIItem name=\"disallowedNodes\" type=\"PlateType\" optional>\n      Override plugin `disallowedNodes`.\n    </APIItem>\n    <APIItem name=\"allowNode\" type=\"AllowNodeConfig\" optional>\n      Override plugin `allowNode`.\n    </APIItem>\n    <APIItem name=\"memoize\" type=\"boolean\" optional>\n      Adds `_memo` property with raw Markdown to top-level blocks for\n      memoization (e.g., with `PlateStatic`). Default: `false`.\n    </APIItem>\n    <APIItem name=\"rules\" type=\"MdRules | null\" optional>\n      Override plugin `rules`.\n    </APIItem>\n    <APIItem name=\"parser\" type=\"ParseMarkdownBlocksOptions\" optional>\n      Options for the underlying Markdown block parser (`parseMarkdownBlocks`).\n      See below.\n    </APIItem>\n    <APIItem name=\"remarkPlugins\" type=\"Plugin[]\" optional>\n      Override plugin `remarkPlugins`.\n    </APIItem>\n    <APIItem name=\"splitLineBreaks\" type=\"boolean\" optional>\n      If `true`, single line breaks (`\\\\n`) in paragraphs become paragraph\n      breaks. Default: `false`.\n    </APIItem>\n    <APIItem name=\"withoutMdx\" type=\"boolean\" optional>\n      If `true`, skips the MDX preprocessing pass and filters `remarkMdx` out\n      of the plugin list. Default: `false`.\n    </APIItem>\n    <APIItem name=\"preserveEmptyParagraphs\" type=\"boolean\" optional>\n      Preserves empty paragraph nodes during deserialization.\n    </APIItem>\n    <APIItem name=\"onError\" type=\"(error: Error) => void\" optional>\n      Receives parser errors before the safe fallback path runs.\n    </APIItem>\n  </APIOptions>\n  <APIReturns type=\"Descendant[]\">An array of Plate nodes.</APIReturns>\n</API>\n\n---\n\n### `api.markdown.deserializeInline`\n\nConverts inline Markdown text into Slate children.\n\n<API name=\"deserializeInline\">\n  <APIParameters>\n    <APIItem name=\"markdown\" type=\"string\">\n      Inline Markdown text to deserialize.\n    </APIItem>\n    <APIItem name=\"options\" type=\"DeserializeMdOptions\" optional>\n      Options for this call, overriding plugin defaults.\n    </APIItem>\n  </APIParameters>\n  <APIReturns type=\"Descendant[]\">Slate children for inline content.</APIReturns>\n</API>\n\n---\n\n### `api.markdown.serialize`\n\nConverts a Plate `Value` (`Descendant[]`) into a Markdown string.\n\n<API name=\"serialize\">\n  <APIParameters>\n    <APIItem name=\"options\" type=\"SerializeMdOptions\" optional>\n      Options for this call, overriding plugin defaults.\n    </APIItem>\n  </APIParameters>\n  <APIOptions type=\"SerializeMdOptions\">\n    <APIItem name=\"value\" type=\"Descendant[]\" optional>\n      Plate nodes to serialize. Defaults to `editor.children`.\n    </APIItem>\n    <APIItem name=\"allowedNodes\" type=\"PlateType\" optional>\n      Override plugin `allowedNodes`.\n    </APIItem>\n    <APIItem name=\"disallowedNodes\" type=\"PlateType\" optional>\n      Override plugin `disallowedNodes`.\n    </APIItem>\n    <APIItem name=\"allowNode\" type=\"AllowNodeConfig\" optional>\n      Override plugin `allowNode`.\n    </APIItem>\n    <APIItem name=\"rules\" type=\"MdRules | null\" optional>\n      Override plugin `rules`.\n    </APIItem>\n    <APIItem name=\"remarkPlugins\" type=\"Plugin[]\" optional>\n      Override plugin `remarkPlugins` (affects stringification).\n    </APIItem>\n    <APIItem name=\"remarkStringifyOptions\" type=\"RemarkStringifyOptions | null\" optional>\n      Options passed to `remark-stringify`. Defaults to plugin options, with\n      Plate setting emphasis to `_` and resource links to `false`.\n    </APIItem>\n    <APIItem name=\"plainMarks\" type=\"PlateType[] | null\" optional>\n      Marks to serialize as plain text instead of Markdown formatting.\n    </APIItem>\n    <APIItem name=\"spread\" type=\"boolean\" optional>\n      Controls spread formatting for list output. Default: `false`.\n    </APIItem>\n    <APIItem name=\"preserveEmptyParagraphs\" type=\"boolean\" optional>\n      Preserves empty paragraph nodes during serialization.\n    </APIItem>\n    <APIItem name=\"withBlockId\" type=\"boolean\" optional>\n      When true, preserves block IDs in markdown serialization to enable AI\n      comment tracking. Wraps blocks with `<block id=\"...\">content</block>`\n      syntax. - **Default:** `false`\n    </APIItem>\n  </APIOptions>\n  <APIReturns type=\"string\">A Markdown string.</APIReturns>\n</API>\n\n---\n\n### `parseMarkdownBlocks`\n\nUtility to parse a Markdown string into block-level tokens (used by `deserialize`, useful with `memoize`).\n\n<API name=\"parseMarkdownBlocks\">\n  <APIParameters>\n    <APIItem name=\"markdown\" type=\"string\">\n      The Markdown string.\n    </APIItem>\n    <APIItem name=\"options\" type=\"ParseMarkdownBlocksOptions\" optional>\n      Parsing options.\n    </APIItem>\n  </APIParameters>\n  <APIOptions type=\"ParseMarkdownBlocksOptions\">\n    <APIItem name=\"exclude\" type=\"string[]\" optional>\n      Marked token types (e.g., `'space'`) to exclude. Default: `['space']`.\n    </APIItem>\n    <APIItem name=\"trim\" type=\"boolean\" optional>\n      Trim trailing whitespace from input. Default: `true`.\n    </APIItem>\n  </APIOptions>\n  <APIReturns type=\"Token[]\">\n    Array of marked `Token` objects with raw Markdown.\n  </APIReturns>\n</API>\n\n## Examples\n\n<Steps>\n\n### Using a Remark Plugin (GFM)\n\nAdd support for GitHub Flavored Markdown (tables, strikethrough, task lists, autolinks).\n\n**Plugin Configuration:**\n\n```tsx title=\"lib/plate-editor.ts\"\nimport { createPlateEditor } from 'platejs/react';\nimport { MarkdownPlugin } from '@platejs/markdown';\nimport remarkGfm from 'remark-gfm';\n// Import Plate plugins for GFM elements\nimport { TablePlugin } from '@platejs/table/react';\nimport { TodoListPlugin } from '@platejs/list-classic/react'; // Ensure this is the correct List plugin for tasks\nimport { StrikethroughPlugin } from '@platejs/basic-nodes/react';\nimport { LinkPlugin } from '@platejs/link/react';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...other plugins\n    TablePlugin,\n    TodoListPlugin, // Or your specific task list plugin\n    StrikethroughPlugin,\n    LinkPlugin,\n    MarkdownPlugin.configure({\n      options: {\n        remarkPlugins: [remarkGfm],\n      },\n    }),\n  ],\n});\n```\n\n**Usage:**\n\n```tsx\nconst markdown = `\nA table:\n\n| a | b |\n| - | - |\n\n~~Strikethrough~~\n\n- [x] Task list item\n\nVisit https://platejs.org\n`;\n\n// Assuming `editor` is your configured Plate editor instance\nconst slateValue = editor.api.markdown.deserialize(markdown);\n// editor.tf.setValue(slateValue); // To set editor content\n\nconst markdownOutput = editor.api.markdown.serialize();\n// markdownOutput will contain GFM syntax\n```\n\n### Customizing Rendering (Syntax Highlighting)\n\nThis example shows two approaches: customizing the rendering component (common for UI changes) and customizing the conversion rule (advanced, for changing Plate structure).\n\n**Background:**\n\n- `@platejs/markdown` converts Markdown fenced code blocks (e.g., \\`\\`\\`js ... \\`\\`\\`) to Plate `code_block` elements with `code_line` children.\n- The Plate `CodeBlockElement` (often from `@platejs/code-block/react`) renders this structure.\n- Syntax highlighting typically occurs within `CodeBlockElement` using a library like `lowlight` (via `CodeBlockPlugin`). See [Code Block Plugin](/docs/code-block) for details.\n\n**Approach 1: Customizing Rendering Component (Recommended for UI)**\n\nTo change how code blocks appear, customize the component for the `code_block` plugin key.\n\n```tsx title=\"components/my-editor.tsx\"\nimport { createPlateEditor } from 'platejs/react';\nimport {\n  CodeBlockPlugin,\n  CodeLinePlugin,\n  CodeSyntaxPlugin,\n} from '@platejs/code-block/react';\nimport { MarkdownPlugin } from '@platejs/markdown';\nimport { MyCustomCodeBlockElement } from './my-custom-code-block'; // Your custom component\n\nconst editor = createPlateEditor({\n  plugins: [\n    CodeBlockPlugin.withComponent(MyCustomCodeBlockElement), // Base plugin for structure/logic\n    CodeLinePlugin.withComponent(MyCustomCodeLineElement),\n    CodeSyntaxPlugin.withComponent(MyCustomCodeSyntaxElement),\n    MarkdownPlugin, // For Markdown conversion\n    // ... other plugins\n  ],\n});\n\n// MyCustomCodeBlockElement.tsx would then implement the desired rendering\n// (e.g., using react-syntax-highlighter), consuming props from PlateElement.\n```\n\nRefer to the [Code Block Plugin documentation](/docs/code-block) for complete examples.\n\n**Approach 2: Customizing Conversion Rule (Advanced - Changing Plate Structure)**\n\nTo fundamentally alter the Plate JSON for code blocks (e.g., storing code as a single string prop), override the `deserialize` rule.\n\n```tsx title=\"lib/plate-editor.ts\"\nimport { MarkdownPlugin } from '@platejs/markdown';\nimport { CodeBlockPlugin } from '@platejs/code-block/react';\n\nMarkdownPlugin.configure({\n  options: {\n    rules: {\n      // Override deserialization for mdast 'code' type\n      code: {\n        deserialize: (mdastNode, deco, options) => {\n          return {\n            type: KEYS.codeBlock, // Use Plate's type\n            lang: mdastNode.lang ?? undefined,\n            rawCode: mdastNode.value || '', // Store raw code directly\n            children: [{ text: '' }], // Plate Element needs a dummy text child\n          };\n        },\n      },\n      // A custom `serialize` rule for `code_block` would also be needed\n      // to convert `rawCode` back to an mdast 'code' node.\n      [KEYS.codeBlock]: {\n        serialize: (slateNode, options) => {\n          return {\n            // mdast 'code' node\n            type: 'code',\n            lang: slateNode.lang,\n            value: slateNode.rawCode,\n          };\n        },\n      },\n    },\n    // remarkPlugins: [...]\n  },\n});\n\n// Your custom rendering component (MyCustomCodeBlockElement) would then\n// need to read the code from the `rawCode` property.\n```\n\nChoose based on whether you're changing UI (Approach 1) or data structure (Approach 2).\n\n### Using Remark Plugins for Math (`remark-math`)\n\nEnable TeX math syntax (`$inline$`, `$$block$$`).\n\n**Plugin Configuration:**\n\n```tsx title=\"lib/plate-editor.ts\"\nimport { createPlateEditor } from 'platejs/react';\nimport { MarkdownPlugin } from '@platejs/markdown';\nimport remarkMath from 'remark-math';\n// Import Plate math plugins for rendering\nimport { MathPlugin } from '@platejs/math/react'; // Main Math plugin\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...other plugins\n    MathPlugin, // Renders block and inline equations\n    MarkdownPlugin.configure({\n      options: {\n        remarkPlugins: [remarkMath],\n        // Default rules handle 'math' and 'inlineMath' mdast types from remark-math,\n        // converting them to Plate's 'equation' and 'inline_equation' types.\n      },\n    }),\n  ],\n});\n```\n\n**Usage:**\n\n```tsx\nconst markdown = `\nInline math: $E=mc^2$\n\nBlock math:\n$$\n\\\\int_a^b f(x) dx = F(b) - F(a)\n$$\n`;\n\n// Assuming `editor` is your configured Plate editor instance\nconst slateValue = editor.api.markdown.deserialize(markdown);\n// slateValue will contain 'inline_equation' and 'equation' nodes.\n\nconst markdownOutput = editor.api.markdown.serialize({ value: slateValue });\n// markdownOutput will contain $...$ and $$...$$ syntax.\n```\n\n### Using Mentions (`remarkMention`)\n\nEnable mention syntax using the link format for consistency and special character support.\n\n**Plugin Configuration:**\n\n```tsx title=\"lib/plate-editor.ts\"\nimport { createPlateEditor } from 'platejs/react';\nimport { MarkdownPlugin, remarkMention } from '@platejs/markdown';\nimport { MentionPlugin } from '@platejs/mention/react';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...other plugins\n    MentionPlugin,\n    MarkdownPlugin.configure({\n      options: {\n        remarkPlugins: [remarkMention],\n      },\n    }),\n  ],\n});\n```\n\n**Supported Format:**\n\n```tsx\nconst markdown = `\nMention: [Alice](mention:alice)\nMention with spaces: [John Doe](mention:john_doe)\nFull name with ID: [Jane Smith](mention:user_123)\n`;\n\n// Assuming `editor` is your configured Plate editor instance\nconst slateValue = editor.api.markdown.deserialize(markdown);\n// Creates mention nodes with appropriate values and display text\n\nconst markdownOutput = editor.api.markdown.serialize({ value: slateValue });\n// All mentions use the link format: [Alice](mention:alice), [John Doe](mention:john_doe), etc.\n```\n\nThe `remarkMention` plugin uses the **[display text](mention:id)** format - a Markdown link-style format that supports spaces and custom display text.\n\nWhen serializing, all mentions use the link format to ensure consistency and support for special characters.\n\n### Using Columns\n\nEnable column layouts with MDX support for multi-column documents.\n\n**Plugin Configuration:**\n\n```tsx title=\"lib/plate-editor.ts\"\nimport { createPlateEditor } from 'platejs/react';\nimport { MarkdownPlugin, remarkMdx } from '@platejs/markdown';\nimport { ColumnPlugin, ColumnItemPlugin } from '@platejs/layout/react';\n\nconst editor = createPlateEditor({\n  plugins: [\n    // ...other plugins\n    ColumnPlugin,\n    ColumnItemPlugin,\n    MarkdownPlugin.configure({\n      options: {\n        remarkPlugins: [remarkMdx], // Required for column MDX syntax\n      },\n    }),\n  ],\n});\n```\n\n**Supported Format:**\n\n```tsx\nconst markdown = `\n<column_group>\n  <column width=\"50%\">\n    Left column content with 50% width\n  </column>\n  <column width=\"50%\">\n    Right column content with 50% width\n  </column>\n</column_group>\n\n<column_group>\n  <column width=\"33%\">First</column>\n  <column width=\"33%\">Second</column>\n  <column width=\"34%\">Third</column>\n</column_group>\n`;\n\n// Assuming `editor` is your configured Plate editor instance\nconst slateValue = editor.api.markdown.deserialize(markdown);\n// Creates column_group with nested column elements\n\nconst markdownOutput = editor.api.markdown.serialize({ value: slateValue });\n// Preserves column structure with width attributes\n```\n\n**Column Features:**\n\n- Supports arbitrary number of columns\n- Width attributes are optional (defaults to equal distribution)\n- Nested content fully supported within columns\n- Width normalization ensures columns always sum to 100%\n\n</Steps>\n\n## Remark Plugins\n\n`@platejs/markdown` leverages the [unified][github-unified] / [remark][github-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)][github-mdast].\n\n**Finding Plugins:**\n\n- [List of remark plugins][github-remark-plugins] (Official)\n- [`remark-plugin` topic on GitHub][github-topic-remark-plugin]\n- [Awesome Remark][github-awesome-remark]\n\n**Common Uses:**\n\n- **Syntax Extensions:** `remark-gfm` (tables, etc.), `remark-math` (TeX), `remark-frontmatter`, `remark-mdx`.\n- **Linting/Formatting:** `remark-lint` (often separate tooling).\n- **Custom Transformations:** Custom plugins to modify mdast.\n\n<Callout type=\"info\" title=\"Remark vs. Rehype\">\n  Plate components (e.g., `TableElement`, `CodeBlockElement`) render Plate JSON.\n  `remarkPlugins` modify the Markdown AST. Unlike some renderers,\n  `rehypePlugins` (for HTML AST) are not part of `MarkdownPlugin`'s conversion\n  pipeline. Run HTML transforms before Plate, or model controlled HTML-like\n  content as MDX plus explicit `rules`.\n</Callout>\n\n## Syntax Support\n\n`@platejs/markdown` uses [`remark-parse`][github-remark-parse], adhering to [CommonMark][commonmark-spec]. Enable GFM or other syntaxes via `remarkPlugins`.\n\n- **Learn Markdown:** [CommonMark Help][commonmark-help]\n- **GFM Spec:** [GitHub Flavored Markdown Spec][gfm-spec]\n\n## Architecture Overview\n\n`@platejs/markdown` bridges Markdown strings and Plate's editor format using the unified/remark ecosystem.\n\n```\n                                             @platejs/markdown\n          +--------------------------------------------------------------------------------------------+\n          |                                                                                            |\n          |  +-----------+        +----------------+        +---------------+      +-----------+       |\n          |  |           |        |                |        |               |      |           |       |\n markdown-+->+ remark    +-mdast->+ remark plugins +-mdast->+ mdast-to-slate+----->+   nodes   +-plate-+->react elements\n          |  |           |        |                |        |               |      |           |       |\n          |  +-----------+        +----------------+        +---------------+      +-----------+       |\n          |       ^                                                                      |             |\n          |       |                                                                      v             |\n          |  +-----------+        +----------------+        +---------------+      +-----------+       |\n          |  |           |        |                |        |               |      |           |       |\n          |  | stringify |<-mdast-+ remark plugins |<-mdast-+ slate-to-mdast+<-----+ serialize |       |\n          |  |           |        |                |        |               |      |           |       |\n          |  +-----------+        +----------------+        +---------------+      +-----------+       |\n          |                                                                                            |\n          +--------------------------------------------------------------------------------------------+\n```\n\n**Key Steps:**\n\n1.  **Parse (Deserialization):**\n    - Markdown string → `remark-parse` → mdast.\n    - `remarkPlugins` transform mdast (e.g., `remark-gfm`).\n    - `mdast-to-slate` converts mdast to Plate nodes using `rules`.\n    - Plate renders nodes via its component system.\n2.  **Stringify (Serialization):**\n    - Plate nodes → `slate-to-mdast` (using `rules`) → mdast.\n    - `remarkPlugins` transform mdast.\n    - `remark-stringify` converts mdast to Markdown string.\n\n<Callout type=\"note\" title=\"Comparison with react-markdown\">\n  - **Direct Node Rendering:** Plate directly renders its nodes via components,\n  unlike `react-markdown` which often uses rehype to convert Markdown to HTML,\n  then to React elements. - **Bidirectional:** Plate's Markdown processor is\n  fully bidirectional. - **Rich Text Integration:** Nodes are integrated with\n  Plate's editing capabilities. - **Plugin System:** Components are managed via\n  Plate's plugin system.\n</Callout>\n\n## Migrating from `react-markdown`\n\nMigrating involves mapping `react-markdown` concepts to Plate's architecture.\n\n**Key Differences:**\n\n1.  **Rendering Pipeline:** `react-markdown` (MD → mdast → hast → React) vs. `@platejs/markdown` (MD ↔ mdast ↔ Plate JSON; Plate components render Plate JSON).\n2.  **Component Customization:**\n    - `react-markdown`: `components` prop replaces HTML tag renderers.\n    - Plate:\n      - `MarkdownPlugin` `rules`: Customize mdast ↔ Plate JSON conversion.\n      - `createPlateEditor` `components`: Customize React components for Plate node types. See [Appendix C](#appendix-c-components).\n3.  **Plugin Ecosystem:** `@platejs/markdown` primarily uses `remarkPlugins`. `rehypePlugins` are less common.\n\n**Mapping Options:**\n\n| `react-markdown` Prop           | `@platejs/markdown` Equivalent/Concept                                                                           | Notes                                                                                  |\n| :------------------------------ | :--------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------- |\n| `children` (string)             | Pass to `editor.api.markdown.deserialize(string)`                                                                | Input for deserialization; often in `createPlateEditor` `value` option.                |\n| `remarkPlugins`                 | `MarkdownPlugin.configure({ options: { remarkPlugins: [...] }})`                                                 | Direct mapping; operates on mdast.                                                     |\n| `rehypePlugins`                 | Not part of `MarkdownPlugin`'s conversion pipeline.                                                              | Run any HTML pipeline before passing Markdown or Plate nodes to Plate.                 |\n| `components={{ h1: MyH1 }}`     | `createPlateEditor({ components: { h1: MyH1 } })`                                                                | Configures Plate rendering component. Key depends on `HeadingPlugin` config.           |\n| `components={{ code: MyCode }}` | 1. **Conversion**: `MarkdownPlugin > rules > code`. 2. **Rendering**: `components: { [KEYS.codeBlock]: MyCode }` | `rules` for mdast (`code`) to Plate (`code_block`). `components` for Plate rendering.  |\n| `allowedElements`               | `MarkdownPlugin.configure({ options: { allowedNodes: [...] }})`                                                  | Filters nodes during conversion (mdast/Plate types).                                   |\n| `disallowedElements`            | `MarkdownPlugin.configure({ options: { disallowedNodes: [...] }})`                                               | Filters nodes during conversion.                                                       |\n| `unwrapDisallowed`              | No direct equivalent. Filtering removes nodes.                                                                   | Custom `rules` could implement unwrapping.                                             |\n| `skipHtml`                      | Default behavior strips most HTML.                                                                               | Sanitize or convert raw HTML before calling `editor.api.markdown.deserialize`.          |\n| `urlTransform`                  | Customize via `rules` for `link` (deserialize) or plugin type (serialize).                                       | Handle URL transformations in conversion rules.                                        |\n| `allowElement`                  | `MarkdownPlugin.configure({ options: { allowNode: { ... } } })`                                                  | Function-based filtering during conversion.                                            |\n\n## Appendix A: HTML in Markdown\n\nBy default, `@platejs/markdown` does **not** process raw HTML tags. Standard Markdown syntax still becomes Plate nodes, but literal HTML like `<div>` is ignored unless you handle it outside Plate or model it as MDX/custom nodes.\n\n`MarkdownPlugin` runs `remark-parse`, configured `remarkPlugins`, and Plate conversion rules. It does not run a rehype HTML stage, so `rehype-raw` and `rehype-sanitize` are not `MarkdownPlugin` options.\n\nFor raw HTML from a trusted source, convert it in your own content pipeline before calling Plate. For untrusted input, sanitize with a strict element and attribute whitelist before deserializing.\n\n```tsx\nconst safeMarkdown = await sanitizeMarkdownBeforePlate(untrustedMarkdown);\n\nconst value = editor.api.markdown.deserialize(safeMarkdown);\n```\n\nFor HTML-like custom nodes that you control, prefer MDX syntax with `remarkMdx` and explicit `rules`. That keeps the conversion in Plate's supported Markdown pipeline.\n\n<Callout type=\"destructive\" title=\"Security Warning\">\n  Raw HTML can carry XSS payloads. Treat untrusted Markdown as unsafe until your\n  own pipeline sanitizes it with a strict element and attribute whitelist.\n</Callout>\n\n## Appendix B: Customizing Conversion Rules (`rules`)\n\nThe `rules` option in `MarkdownPlugin.configure` offers fine-grained control over mdast ↔ Plate JSON conversion. Keys in the `rules` object match node types.\n\n- **Deserialization (Markdown → Plate):** 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 Plate `Descendant` or `Descendant[]`.\n- **Serialization (Plate → 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.\n\n**Example: Overriding Link Deserialization**\n\n```tsx title=\"lib/plate-editor.ts\"\nMarkdownPlugin.configure({\n  options: {\n    rules: {\n      // Rule for mdast 'link' type\n      link: {\n        deserialize: (mdastNode, deco, options) => {\n          // Default creates { type: 'a', url: ..., children: [...] }\n          // Add a custom property:\n          return {\n            type: 'a', // Plate link element type\n            url: mdastNode.url,\n            title: mdastNode.title,\n            customProp: 'added-during-deserialize',\n            children: convertChildrenDeserialize(\n              mdastNode.children,\n              deco,\n              options\n            ),\n          };\n        },\n      },\n      // Rule for Plate 'a' type (if serialization needs override for customProp)\n      a: {\n        // Assuming 'a' is the Plate type for links\n        serialize: (slateNode, options) => {\n          // Default creates mdast 'link'\n          // Handle customProp if needed in MDX attributes or similar\n          return {\n            type: 'link', // mdast type\n            url: slateNode.url,\n            title: slateNode.title,\n            // customProp: slateNode.customProp, // MDX attribute?\n            children: convertNodesSerialize(slateNode.children, options),\n          };\n        },\n      },\n    },\n    // ... remarkPlugins ...\n  },\n});\n```\n\n**Default Rules Summary:**\nRefer to [`defaultRules.ts`](https://github.com/udecode/plate/blob/main/packages/markdown/src/lib/rules/defaultRules.ts) for the complete list. Key conversions include:\n\n| Markdown (mdast)    | Plate Type             | Notes                                          |\n| :------------------ | :--------------------- | :--------------------------------------------- |\n| `paragraph`         | `p`                    |                                                |\n| `heading` (depth)   | `h1` - `h6`            | Based on depth.                                |\n| `blockquote`        | `blockquote`           |                                                |\n| `list` (ordered)    | `ol` / `p`\\*           | `ol`/`li`/`lic` or `p` with list indent props. |\n| `list` (unordered)  | `ul` / `p`\\*           | `ul`/`li`/`lic` or `p` with list indent props. |\n| `code` (fenced)     | `code_block`           | Contains `code_line` children.                 |\n| `inlineCode`        | `code` (mark)          | Applied to text.                               |\n| `strong`            | `bold` (mark)          | Applied to text.                               |\n| `emphasis`          | `italic` (mark)        | Applied to text.                               |\n| `delete`            | `strikethrough` (mark) | Applied to text.                               |\n| `link`              | `a`                    |                                                |\n| `image`             | `img`                  | Wraps in paragraph during serialization.       |\n| `thematicBreak`     | `hr`                   |                                                |\n| `table`             | `table`                | Contains `tr`.                                 |\n| `math` (block)      | `equation`             | Requires `remark-math`.                        |\n| `inlineMath`        | `inline_equation`      | Requires `remark-math`.                        |\n| `mdxJsxFlowElement` | _Custom_               | Requires `remark-mdx` and custom `rules`.      |\n| `mdxJsxTextElement` | _Custom_               | Requires `remark-mdx` and custom `rules`.      |\n\n\\* List conversion depends on `ListPlugin` detection.\n\n**Emoji shortcodes:** Add [`remark-emoji`](https://github.com/rhysd/remark-emoji) to `remarkPlugins` to turn `:fire:` into unicode `🔥` on deserialization and back to unicode on serialization.\n\n**GFM footnotes:** With `remark-gfm` enabled, footnotes deserialize into `footnoteReference` and `footnoteDefinition` nodes. Add the matching [Footnote plugins](/docs/footnote) to render them as real editor nodes instead of falling back to unknown types.\n\n---\n\n**Default MDX Conversions (with `remark-mdx`):**\n\n| MDX (mdast)                            | Plate Type               | Notes                                       |\n| :------------------------------------- | :----------------------- | :------------------------------------------ |\n| `<del>...</del>`                       | `strikethrough` (mark)   | Alt for `~~strikethrough~~`                 |\n| `<sub>...</sub>`                       | `subscript` (mark)       | H<sub>2</sub>O                              |\n| `<sup>...</sup>`                       | `superscript` (mark)     | E=mc<sup>2</sup>                            |\n| `<u>...</u>`                           | `underline` (mark)       | <u>Underlined</u>                           |\n| `<mark>...</mark>`                     | `highlight` (mark)       | <mark>Highlighted</mark>                    |\n| `<span style=\"font-family: ...\">`      | `fontFamily` (mark)      |                                             |\n| `<span style=\"font-size: ...\">`        | `fontSize` (mark)        |                                             |\n| `<span style=\"font-weight: ...\">`      | `fontWeight` (mark)      |                                             |\n| `<span style=\"color: ...\">`            | `color` (mark)           |                                             |\n| `<span style=\"background-color: ...\">` | `backgroundColor` (mark) |                                             |\n| `<date>...</date>`                     | `date`                   | Custom Date element                         |\n| `[text](mention:id)`                   | `mention`                | Custom Mention element                      |\n| `<file name=\"...\" />`                  | `file`                   | Custom File element                         |\n| `<audio src=\"...\" />`                  | `audio`                  | Custom Audio element                        |\n| `<video src=\"...\" />`                  | `video`                  | Custom Video element                        |\n| `<toc />`                              | `toc`                    | Table of Contents                           |\n| `<callout>...</callout>`               | `callout`                | Callout block                               |\n| `<column_group>...</column_group>`     | `column_group`           | Multi-column layout container               |\n| `<column width=\"50%\">...</column>`     | `column`                 | Single column with optional width attribute |\n\n## Appendix C: Components for Rendering\n\nWhile `rules` handle MD ↔ Plate conversion, Plate uses React components to _render_ Plate nodes. Configure these in `createPlateEditor` via the `components` option or plugin `withComponent` method.\n\n**Example:**\n\n```tsx title=\"components/my-editor.tsx\"\nimport { createPlateEditor, ParagraphPlugin, PlateLeaf } from 'platejs/react';\nimport { BoldPlugin } from '@platejs/basic-nodes/react';\nimport { CodeBlockPlugin } from '@platejs/code-block/react';\nimport { ParagraphElement } from '@/components/ui/paragraph-node'; // Example UI component\nimport { CodeBlockElement } from '@/components/ui/code-block-node'; // Example UI component\n\nconst editor = createPlateEditor({\n  plugins: [\n    ParagraphPlugin.withComponent(ParagraphElement),\n    CodeBlockPlugin.withComponent(CodeBlockElement),\n    BoldPlugin,\n    /* ... */\n  ],\n});\n```\n\nRefer to [Plugin Components](/docs/plugin-components) for more on creating/registering components.\n\n## Appendix D: `PlateMarkdown` Component (Read-Only Display)\n\nFor a `react-markdown`-like component for read-only display:\n\n```tsx title=\"components/plate-markdown.tsx\"\nimport React, { useEffect } from 'react';\nimport { Plate, PlateContent, usePlateEditor } from 'platejs/react';\nimport { MarkdownPlugin } from '@platejs/markdown';\n// Import necessary Plate plugins for common Markdown features\nimport { HeadingPlugin } from '@platejs/basic-nodes/react';\n// ... include other plugins like BlockquotePlugin, CodeBlockPlugin, ListPlugin, etc.\n// ... and mark plugins like BoldPlugin, ItalicPlugin, etc.\n\nexport interface PlateMarkdownProps {\n  children: string; // Markdown content\n  remarkPlugins?: any[];\n  components?: Record<string, React.ComponentType<any>>; // Plate component overrides\n  className?: string;\n}\n\nexport function PlateMarkdown({\n  children,\n  remarkPlugins = [],\n  components = {},\n  className,\n}: PlateMarkdownProps) {\n  const editor = usePlateEditor({\n    plugins: [\n      // Include all plugins needed to render your Markdown\n      HeadingPlugin /* ... other plugins ... */,\n      MarkdownPlugin.configure({ options: { remarkPlugins } }),\n    ],\n    components, // Pass through component overrides\n  });\n\n  useEffect(() => {\n    editor.tf.reset(); // Clear previous content\n    editor.tf.setValue(\n      editor.getApi(MarkdownPlugin).markdown.deserialize(children)\n    );\n  }, [children, editor, remarkPlugins]); // Re-deserialize if markdown or plugins change\n\n  return (\n    <Plate editor={editor}>\n      <PlateContent readOnly className={className} />\n    </Plate>\n  );\n}\n\n// Usage Example:\n// const markdownString = \"# Hello\\nThis is *Markdown*.\";\n// <PlateMarkdown className=\"prose dark:prose-invert\">\n//   {markdownString}\n// </PlateMarkdown>\n```\n\n<Callout type=\"info\" title=\"Initial Value\">\n  This `PlateMarkdown` component provides a **read-only** view. For full\n  editing, see the [Installation guides](/docs/installation).\n</Callout>\n\n## Security Considerations\n\n`@platejs/markdown` prioritizes safety by converting Markdown to a structured Plate format, avoiding direct HTML rendering. However, security depends on:\n\n- **Custom `rules`:** Ensure `deserialize` rules don't introduce unsafe data.\n- **`remarkPlugins`:** Vet third-party remark plugins for potential security risks.\n- **Raw HTML Processing:** Sanitize or convert raw HTML before passing Markdown to Plate. Treat untrusted Markdown as unsafe until your own pipeline has applied a strict whitelist.\n- **Plugin Responsibility:** URL validation in `LinkPlugin` ([`isUrl`](/docs/link#linkplugin)) or `MediaEmbedPlugin` ([`parseMediaUrl`](/docs/media#parsemediaurl)) is crucial.\n\n**Recommendation:** Treat untrusted Markdown input cautiously. Sanitize if allowing complex features or raw HTML.\n\n## Related Links\n\n- **[remark][github-remark]:** Markdown processor.\n- **[unified][github-unified]:** Core processing engine.\n- **[MDX][github-mdx]:** JSX in Markdown.\n- **[react-markdown][github-react-markdown]:** Alternative React Markdown component.\n- **[remark-slate-transformer][github-remark-slate-transformer]:** Initial mdast ↔ Plate conversion work by [inokawa](https://github.com/inokawa).\n\n[commonmark-help]: https://commonmark.org/help/\n[commonmark-spec]: https://spec.commonmark.org/\n[gfm-spec]: https://github.github.com/gfm/\n[github-awesome-remark]: https://github.com/remarkjs/awesome-remark\n[github-mdast]: https://github.com/syntax-tree/mdast\n[github-mdx]: https://mdxjs.com/\n[github-react-markdown]: https://github.com/remarkjs/react-markdown\n[github-remark-slate-transformer]: https://github.com/inokawa/remark-slate-transformer\n[github-rehype-raw]: https://github.com/rehypejs/rehype-raw\n[github-rehype-sanitize]: https://github.com/rehypejs/rehype-sanitize\n[github-remark]: https://github.com/remarkjs/remark\n[github-remark-gfm]: https://github.com/remarkjs/remark-gfm\n[github-remark-parse]: https://github.com/remarkjs/remark/tree/main/packages/remark-parse\n[github-remark-plugins]: https://github.com/remarkjs/remark/blob/main/doc/plugins.md#list-of-plugins\n[github-remark-stringify]: https://github.com/remarkjs/remark/tree/main/packages/remark-stringify\n[github-topic-remark-plugin]: https://github.com/topics/remark-plugin\n[github-unified]: https://github.com/unifiedjs/unified\n",
      "type": "registry:file",
      "target": "content/docs/plate/(plugins)/(serializing)/markdown.mdx"
    }
  ],
  "type": "registry:file"
}