deserialize).serialize).dangerouslySetInnerHTML.remarkPlugins.remark-gfm.While libraries like react-markdown render Markdown to React elements, @platejs/markdown offers deeper integration with the Plate ecosystem:
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, @platejs/markdown is the
integrated solution.
The fastest way to add Markdown functionality is with MarkdownKit. It combines the configured MarkdownPlugin with the live Footnote plugins and essential remark plugins for Plate UI.
import { MarkdownPlugin, remarkMdx, remarkMention } from '@platejs/markdown';
import { PLUGINS } from 'platejs';
import remarkEmoji from 'remark-emoji';
import remarkGfm from 'remark-gfm';
import remarkMath from 'remark-math';
export const MarkdownKit = [
MarkdownPlugin.configure(({ editor }) => {
const comment = editor.plugin(PLUGINS.comment);
const suggestion = editor.plugin(PLUGINS.suggestion);
const plainMarks: string[] = [];
if (suggestion.installed) {
plainMarks.push(suggestion.schema.key);
}
if (comment.installed) {
plainMarks.push(comment.schema.key);
}
return {
initialState: {
plainMarks,
remarkPlugins: [
remarkMath,
remarkGfm,
remarkEmoji,
remarkMdx,
remarkMention,
],
},
};
}),
];import { MarkdownPlugin, remarkMdx, remarkMention } from '@platejs/markdown';
import { PLUGINS } from 'platejs';
import remarkEmoji from 'remark-emoji';
import remarkGfm from 'remark-gfm';
import remarkMath from 'remark-math';
export const MarkdownKit = [
MarkdownPlugin.configure(({ editor }) => {
const comment = editor.plugin(PLUGINS.comment);
const suggestion = editor.plugin(PLUGINS.suggestion);
const plainMarks: string[] =
import { createPlateEditor } from 'platejs/react';
import { MarkdownKit } from '@/components/editor/markdown';
const editor = createPlateEditor({
plugins: [
// ...otherPlugins,
...MarkdownKit,
],
});import { createPlateEditor } from 'platejs/react';
import { MarkdownKit } from '@/components/editor/markdown';
const editor = createPlateEditor
import { MarkdownPlugin } from '@platejs/markdown';
import { createPlateEditor } from 'platejs/react';
const editor = createPlateEditor({
plugins: [
// ...otherPlugins,
MarkdownPlugin,
],
});import { MarkdownPlugin } from '@platejs/markdown';
import { createPlateEditor } from 'platejs/react';
const editor = createPlateEditor({
plugins: [
// ...otherPlugins,
MarkdownPlugin,
],
});Configure MarkdownPlugin.initialState for shared syntax plugins, filtering,
and stringification. Installed feature plugins contribute their Markdown node
codecs automatically. The same state drives the Markdown API and the plugin's
text/markdown and text/plain document codecs.
import { createPlateEditor } from 'platejs/react';
import {
MarkdownPlugin,
remarkMention,
remarkMdx,
} from '@platejs/markdown';
import remarkEmoji from 'remark-emoji';
import remarkGfm from 'remark-gfm';
import remarkMath from 'remark-math';
const editor = createPlateEditor({
plugins: [
// ...other Plate plugins
MarkdownPlugin.configure({
initialState: {
remarkPlugins: [
remarkMath,
remarkGfm,
remarkEmoji,
MarkdownPlugin publishes conversion through editor.api.markdown. Plugin
state remains under editor.plugin(MarkdownPlugin). Add or remove a feature
plugin to add or remove its Markdown conversion.
Markdown is a projection of document.children. It does not preserve named
roots or open slice edges. Clipboard transport uses
application/x-plite-fragment when exact roots, openStart, and openEnd
must survive.
Use editor.api.markdown.deserialize to convert a Markdown string into an
EditorDocumentValue. Media captions are block children of their media
element.
import { createPlateEditor } from 'platejs/react';
import { MarkdownPlugin } from '@platejs/markdown';
import { ItalicPlugin } from '@platejs/basic-nodes/react';
import { BasicBlocksKit } from '@/components/editor/basic-blocks';
// ... import other necessary Plate plugins for rendering elements
const markdownString = '# Hello, *Plate*!';
const editor = createPlateEditor({
plugins: [
// MarkdownPlugin must be included
MarkdownPlugin,
...BasicBlocksKit,
ItalicPlugin,
],
// Use deserialize in the value factory for initial content
initialValue: ({ editor })
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.
Use editor.api.markdown.serialize to convert the current editor document or a specific EditorDocumentValue into a Markdown string.
Serialization reads the document's primary children. Named roots are outside
Markdown's representation.
Serializing Current Editor Content:
// Assuming `editor` is your Plate editor instance with content
const markdownOutput = editor.api.markdown.serialize();
console.info(markdownOutput);// Assuming `editor` is your Plate editor instance with content
const markdownOutput = editor.api.markdown.serialize();
console.info(markdownOutput);Serializing a Specific Document:
const specificNodes = [
{
children: [{ text: 'Serialize just this paragraph.' }],
type: 'paragraph',
},
{
children: [{ text: 'And this heading.' }],
type: 'heading', level: 1,
},
];
// Assuming `editor` is your Plate editor instance
const partialMarkdownOutput = editor.api.markdown.serialize({
value: { children: specificNodes },
});
console.info(partialMarkdownOutput);Markdown image alt text maps to one paragraph child of the image. Rich MDX media children map directly to the media element's block children.
A key feature is handling custom Plate elements that lack standard Markdown
representation, such as underline and mentions. @platejs/markdown converts
these to MDX elements during serialization and parses them back
during deserialization.
Example: Handling a custom date element.
Plate Node Structure:
{
children: [
{ text: 'Today is ' },
{
children: [{ text: '' }],
date: '2025-03-31',
type: 'date',
},
],
type: 'paragraph',
}{
children: [
{ text: 'Today is ' },
{
children: [{ text: '' }],
date: '2025-03-31'
Feature plugin codec:
@platejs/core includes the Markdown codec authoring types. Feature plugins
declare their codecs without importing the optional Markdown runtime.
When one plugin owns both HTML and Markdown codecs, declare both format keys in
one defineCodecs object. Each format keeps its schema-specific inference.
import { defineBasePlugin } from '@platejs/core';
import { property } from '@platejs/plite';
export const BaseDatePlugin = defineBasePlugin('date', {
codecs: ({ defineCodecs, schema: { type } }) =>
defineCodecs({
'text/html': {
decode: ({ element }) => ({ date: element.dataset.date }),
encode: ({ node }) => ({
attributes: { 'data-date': node.date },
tag: 'span',
}),
match: [{ attributes: {
Conversion Process:
date node writes as <date value="2025-03-31" />.<date value="2025-03-31" /> and <date>2025-03-31</date> convert back to the Plate date node.Pass Markdown conversion behavior through MarkdownPlugin.configure({ initialState }).
Whitelist specific node types (Plate 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 → Plate. Return true to keep. -
serialize?: (node: Descendant) => boolean: Filter for Plate → Markdown.
Return true to keep.
Array of remark
plugins
(e.g., remark-gfm, remark-math, remark-mdx). Operates on Markdown
AST (mdast). Default: [].
Options passed to remark-stringify. Default: null.
Marks serialized as plain text instead of Markdown formatting. Default:
null.
MarkdownPlugin exposes conversion methods through editor.api.markdown and
these product codecs:
| Format | Decode | Encode | Representation |
|---|---|---|---|
text/markdown | Yes | Yes | Closed primary-content slice |
text/plain | Yes | No | Closed primary-content slice |
The generic clipboard pipeline prefers application/x-plite-fragment for an
exact Plate slice and delegates to these external formats when needed.
Converts a Markdown string into an EditorDocumentValue.
Override plugin allowedNodes.
Override plugin disallowedNodes.
Override plugin allowNode.
Override compiled feature codecs for this deserialization call.
Override plugin remarkPlugins.
If true, single line breaks (\\n) in paragraphs become paragraph
breaks. Default: false.
If true, skips the MDX preprocessing pass and filters remarkMdx out
of the plugin list. Default: false.
Preserves empty paragraph nodes during deserialization.
Receives parser errors before the safe fallback path runs.
Converts inline Markdown text into Plite children.
Converts an EditorDocumentValue into a Markdown string.
Plate document to serialize. Defaults to editor.read.value(). Only
value.children is represented; named roots are not serialized.
Override plugin allowedNodes.
Override plugin disallowedNodes.
Override plugin allowNode.
Override compiled feature codecs for this serialization call.
Override plugin remarkPlugins (affects stringification).
Options passed to remark-stringify. Defaults to the shared Markdown
configuration, with
Plate setting emphasis to _ and resource links to false.
Marks to serialize as plain text instead of Markdown formatting.
Controls spread formatting for list output. Default: false.
Preserves empty paragraph nodes during serialization.
Serializes IDs from ElementIdPlugin as
<block id="...">content</block>. The editor must install
ElementIdPlugin. Deserialization restores each wrapper's persisted ID.
falseUtility to parse a Markdown string into block-level tokens.
Add support for GitHub Flavored Markdown: tables, strikethrough, task lists, and autolinks.
Plugin Configuration:
import { createPlateEditor } from 'platejs/react';
import { MarkdownPlugin } from '@platejs/markdown';
import remarkGfm from 'remark-gfm';
// Import Plate plugins for GFM elements
import { TablePlugin } from '@platejs/table/react';
import { TodoListPlugin } from '@platejs/list-classic/react'; // Ensure this is the correct List plugin for tasks
import { StrikethroughPlugin } from '@platejs/basic-nodes/react';
import { LinkPlugin } from '@platejs/link/react';
const editor = createPlateEditor({
plugins: [
// ...other plugins
TablePlugin,
TodoListPlugin, // Or your specific task list plugin
StrikethroughPlugin,
LinkPlugin,
MarkdownPlugin.configure({
initialState: {
remarkPlugins: [remarkGfm],
},
}),
],
});import { createPlateEditor } from 'platejs/react';
import { MarkdownPlugin } from '@platejs/markdown';
import remarkGfm from 'remark-gfm';
// Import Plate plugins for GFM elements
import { TablePlugin } from '@platejs/table/react';
import { TodoListPlugin } from '@platejs/list-classic/react'; // Ensure this is the correct List plugin for tasks
import { StrikethroughPlugin } from '@platejs/basic-nodes/react';
import { LinkPlugin } from '@platejs/link/react';
const editor = createPlateEditor({
plugins: [
// ...other plugins
TablePlugin,
TodoListPlugin, // Or your specific task list plugin
StrikethroughPlugin,
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 document = editor.api.markdown.deserialize(markdown);
editor.update((tx) => {
tx.value.replace(document);
});
const markdownOutput = editor.api.markdown.serialize();
// markdownOutput will contain GFM syntaxThis example shows two approaches: customizing the rendering component for UI changes and customizing one conversion operation for a different Plate shape.
Background:
@platejs/markdown converts Markdown fenced code blocks (e.g., ```js ...
```) to Plate codeBlock elements with codeLine children.CodeBlockElement (often from @platejs/code-block/react) renders this structure.CodeHighlightPlugin and a library like lowlight. See Code Block Plugin for details.Approach 1: Customizing Rendering Component (Recommended for UI)
To change how code blocks appear, configure the CodeBlockPlugin descriptor.
import { createPlateEditor } from 'platejs/react';
import {
CodeBlockPlugin,
CodeHighlightPlugin,
CodeLinePlugin,
} from '@platejs/code-block/react';
import { MarkdownPlugin } from '@platejs/markdown';
import { MyCustomCodeBlockElement } from './my-custom-code-block'; // Your custom component
const editor = createPlateEditor({
plugins: [
CodeBlockPlugin.configure({ component: MyCustomCodeBlockElement }),
CodeLinePlugin.configure({ component: MyCustomCodeLineElement }),
CodeHighlightPlugin.configure({ component: MyCustomCodeSyntaxElement }),
MarkdownPlugin,
// ... other plugins
Refer to the Code Block Plugin documentation for complete examples.
Approach 2: One-operation conversion override (Advanced)
To alter the Plate JSON for one import, pass a rules override to that
operation. Reusable code-block behavior belongs in the code-block feature
plugin's text/markdown codec.
import { CodeBlockPlugin } from '@platejs/code-block/react';
const codeBlock = editor.plugin(CodeBlockPlugin);
const value = editor.api.markdown.deserialize(markdown, {
rules: {
[codeBlock.name]: {
deserialize: (mdastNode) => ({
children: [{ text: '' }],
language: mdastNode.lang ?? undefined,
rawCode: mdastNode.value || '',
type: codeBlock.schema.type,
}),
},
},
});
const output = editor.api.markdown.
Choose based on whether you're changing UI (Approach 1) or data structure (Approach 2).
remark-math)Enable TeX math syntax ($inline$, $$block$$).
Plugin Configuration:
import { createPlateEditor } from 'platejs/react';
import { MarkdownPlugin } from '@platejs/markdown';
import remarkMath from 'remark-math';
// Import Plate math plugins for rendering
import {
EquationPlugin,
InlineEquationPlugin,
} from '@platejs/math/react';
const editor = createPlateEditor({
plugins: [
// ...other plugins
EquationPlugin,
InlineEquationPlugin,
MarkdownPlugin.configure({
initialState: {
remarkPlugins: [remarkMath],
// The installed math plugins own the `math` and `inlineMath` codecs.
},
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 document = editor.api.markdown.deserialize(markdown);
// document.children contains 'inlineEquation' and 'equation' nodes.
const markdownOutput = editor.api.markdown.serialize({ value: document });
// markdownOutput will contain $...$ and $$...$$ syntax.
remarkMention)Enable mention syntax using the link format for consistency and special character support.
Plugin Configuration:
import { createPlateEditor } from 'platejs/react';
import {
MarkdownPlugin,
remarkMention,
} from '@platejs/markdown';
import { MentionPlugin } from '@platejs/mention/react';
const editor = createPlateEditor({
plugins: [
// ...other plugins
MentionPlugin,
MarkdownPlugin.configure({
initialState: {
remarkPlugins: [remarkMention],
},
}),
],
});Supported Format:
const markdown = `
Mention: [Alice](mention:alice)
Mention with spaces: [John Doe](mention:john_doe)
Full name with ID: [Jane Smith](mention:user_123)
`;
// Assuming `editor` is your configured Plate editor instance
const value = editor.api.markdown.deserialize(markdown);
// Creates mention nodes with appropriate values and display text
const markdownOutput = editor.api.markdown.serialize({ value });
// All mentions use the link format: [Alice](mention:alice), [John Doe](mention:john_doe), etc.
The remarkMention plugin uses the display text format - a Markdown link-style format that supports spaces and custom display text.
When serializing, all mentions use the link format to ensure consistency and support for special characters.
Enable column layouts with MDX support for multi-column documents.
Plugin Configuration:
import { createPlateEditor } from 'platejs/react';
import {
MarkdownPlugin,
remarkMdx,
} from '@platejs/markdown';
import { ColumnPlugin, ColumnItemPlugin } from '@platejs/layout/react';
const editor = createPlateEditor({
plugins: [
// ...other plugins
ColumnPlugin,
ColumnItemPlugin,
MarkdownPlugin.configure({
initialState: {
remarkPlugins: [remarkMdx], // Required for column MDX syntax
},
}),
],
});Supported Format:
const markdown = `
<columnGroup>
<column width="50%">
Left column content with 50% width
</column>
<column width="50%">
Right column content with 50% width
</column>
</columnGroup>
<columnGroup>
<column width="33%">First</column>
<column width="33%">Second</column>
<column width="34%">Third</column>
</columnGroup>
`;
// Assuming `editor` is your configured Plate editor instance
const value = editor.api.markdown.deserialize(markdown);
// Creates a columnGroup node with nested column elements
const markdownOutput = editor.api.markdown.serialize({ value });
// Preserves column structure with width attributesColumn Features:
@platejs/markdown leverages the unified / remark ecosystem. Extend its capabilities through the remarkPlugins option. These plugins operate on the mdast (Markdown Abstract Syntax Tree).
Finding Plugins:
Common Uses:
remark-gfm (tables, etc.), remark-math (TeX), remark-frontmatter, remark-mdx.remark-lint (often separate tooling).Plate components (e.g., TableElement, CodeBlockElement) render Plate JSON.
remarkPlugins modify the Markdown AST. Unlike some renderers,
rehypePlugins (for HTML AST) are not part of MarkdownPlugin's conversion
pipeline. Run HTML transforms before Plate, or model controlled HTML-like
content as MDX plus a feature-owned Markdown codec.
@platejs/markdown uses remark-parse, adhering to CommonMark. Enable GFM or other syntaxes via remarkPlugins.
@platejs/markdown bridges Markdown strings and Plate's editor format using the unified/remark ecosystem.
@platejs/markdown
+--------------------------------------------------------------------------------------------+
| |
| +-----------+ +----------------+ +---------------+ +-----------+ |
| | | | | | | | | |
markdown-+->+ remark +-mdast->+ remark plugins +-mdast->+ mdast-to-plate+----->+ nodes +-plate-+->react elements
| | | | | | | | | |
| +-----------+ +----------------+ +---------------+ +-----------+ |
| ^ | |
| | v |
| +-----------+ +----------------+ +---------------+ +-----------+ |
| | | | | | | | | |
| | stringify |<-mdast-+ remark plugins |<-mdast-+ plate-to-mdast+<-----+ serialize | |
| | | | | | | | | |
| +-----------+ +----------------+ +---------------+ +-----------+ |
| |
+--------------------------------------------------------------------------------------------+
Key Steps:
remark-parse → mdast.remarkPlugins transform mdast (e.g., remark-gfm).remarkPlugins transform mdast.remark-stringify converts mdast to Markdown string.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 involves mapping react-markdown concepts to Plate's architecture.
Key Differences:
react-markdown (MD → mdast → hast → React) vs. @platejs/markdown (MD ↔ mdast ↔ Plate JSON; Plate components render Plate JSON).react-markdown: components prop replaces HTML tag renderers.codecs: Customize reusable mdast ↔ Plate JSON
conversion.rules: Override conversion for one API call.createPlateEditor components: Customize React components for Plate node types. See Appendix C.@platejs/markdown uses remarkPlugins. rehypePlugins are not part of its conversion pipeline.Mapping Options:
The option examples use import { MarkdownPlugin } from '@platejs/markdown'.
react-markdown Prop | @platejs/markdown Equivalent/Concept | Notes |
|---|---|---|
children (string) | Pass to editor.api.markdown.deserialize(string) | Input for deserialization; often in createPlateEditor initialValue. |
remarkPlugins | MarkdownPlugin.configure({ initialState: { remarkPlugins: [...] } }) | Operates on mdast. |
rehypePlugins | Not part of MarkdownPlugin's conversion pipeline. | Run any HTML pipeline before passing Markdown or Plate nodes to Plate. |
components={{ h1: MyH1 }} | createPlateEditor({ components: { h1: MyH1 } }) | Configures the component for the default h1 persisted element type. |
components={{ code: MyCode }} | Feature plugin text/markdown codec plus components: { ['codeBlock']: MyCode } | The codec owns mdast ↔ Plate conversion; the component owns rendering. |
allowedElements | MarkdownPlugin.configure({ initialState: { allowedNodes: [...] } }) | Filters nodes during conversion (mdast/Plate types). |
disallowedElements | MarkdownPlugin.configure({ initialState: { disallowedNodes: [...] } }) | Filters nodes during conversion. |
unwrapDisallowed | No direct equivalent. Filtering removes nodes. | A one-operation rules override can implement unwrapping. |
skipHtml | Default behavior strips most HTML. | Sanitize or convert raw HTML before calling editor.api.markdown.deserialize. |
urlTransform | Configure the link feature codec, or pass a one-operation rules override. | Reusable policy belongs with the feature. |
allowElement | MarkdownPlugin.configure({ initialState: { allowNode: { ... } } }) | Function-based filtering during conversion. |
By 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.
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 state.
For 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.
const safeMarkdown = await sanitizeMarkdownBeforePlate(untrustedMarkdown);
const value = editor.api.markdown.deserialize(safeMarkdown);const safeMarkdown = await sanitizeMarkdownBeforePlate(untrustedMarkdown);
const value = editor.api.markdown.deserialize(safeMarkdown);For HTML-like custom nodes that you control, prefer MDX syntax with remarkMdx
and a codec on the owning feature plugin. That keeps the conversion in Plate's
supported Markdown pipeline.
Raw HTML can carry XSS payloads. Treat untrusted Markdown as unsafe until your own pipeline sanitizes it with a strict element and attribute whitelist.
Reusable conversion lives on the owning plugin under
codecs['text/markdown']. MarkdownPlugin compiles codecs from installed
plugins, so removing a feature also removes its conversion. Use rules on
deserialize or serialize only when one operation needs a different result.
from names the mdast node type or MDX tag. decode
receives a typed node plus helpers for children, attributes, captions, and
the owning plugin's resolved schema metadata.encode receives the schema-inferred Plate node plus
helpers that guarantee phrasing, block, or flow content.rules keys use installed feature names, such
as codeBlock and link.rules keys match the persisted node type.Example: Overriding Link Deserialization
import { convertChildrenDeserialize } from '@platejs/markdown';
import { LinkPlugin } from '@platejs/link/react';
const link = editor.plugin(LinkPlugin);
editor.api.markdown.deserialize(markdown, {
rules: {
[link.name]: {
deserialize: (node, decoration, options) => ({
children: convertChildrenDeserialize(
node.children,
decoration,
options
),
customProp: 'this import only',
title: node.title,
type: link.schema.type,
url: node.url,
}),
},
},
});import { convertChildrenDeserialize } from '@platejs/markdown';
import { LinkPlugin } from '@platejs/link/react';
const link = editor.plugin(LinkPlugin);
editor.api.markdown.deserialize(markdown, {
rules: {
[link.name]: {
deserialize: (node, decoration, options) => ({
children: convertChildrenDeserialize(
node.children,
decoration,
options
),
customProp: 'this import only',
title: node.title,
type: link.schema.type,
url: node.url,
}),
Installed feature codecs:
| Owner package | Markdown nodes |
|---|---|
@platejs/markdown | Paragraphs, text, breaks, raw HTML |
@platejs/basic-nodes | Headings, blockquotes, thematic breaks, basic marks |
@platejs/link | Links |
@platejs/list, @platejs/list-classic | Indent and classic lists |
@platejs/code-block, @platejs/math | Code and math |
@platejs/media | Images, files, audio, video, embeds |
@platejs/table, @platejs/layout | Tables and columns |
| Feature packages | Dates, mentions, footnotes, callouts, comments, suggestions, TOC |
Emoji shortcodes: Add remark-emoji to remarkPlugins to turn :fire: into unicode 🔥 on deserialization and back to unicode on serialization.
GFM footnotes: With remark-gfm enabled, footnotes deserialize into footnoteReference and footnoteDefinition nodes. Add the matching Footnote plugins to render them as real editor nodes instead of falling back to unknown types.
Feature-owned MDX conversions (with remark-mdx):
| MDX (mdast) | Plate Type | Notes |
|---|---|---|
<del>...</del> | strikethrough (mark) | Alt for ~~strikethrough~~ |
<sub>...</sub> | script: 'sub' (mark) | H2O |
<sup>...</sup> | script: 'sup' (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> | date | Custom Date element |
[text](mention:id) | mention | Custom Mention element |
<file name="..." /> | file | Custom File element |
<audio src="..." /> | audio | Custom Audio element |
<video src="..." /> | video | Custom Video element |
<toc /> | toc | Table of Contents |
<callout>...</callout> | callout | Callout block |
<columnGroup>...</columnGroup> | columnGroup | Multi-column layout container |
<column width="50%">...</column> | column | Single column with optional width attribute |
While feature codecs handle MD ↔ Plate conversion, Plate uses React components
to render Plate nodes. Configure these in createPlateEditor via the
components option or plugin component method.
Example:
import { createPlateEditor, ParagraphPlugin, PlateLeaf } from 'platejs/react';
import { BoldPlugin } from '@platejs/basic-nodes/react';
import { CodeBlockPlugin } from '@platejs/code-block/react';
import { ParagraphElement } from '@/components/editor/paragraph'; // Example UI component
import { CodeBlockElement } from '@/components/editor/code-block'; // Example UI component
const editor = createPlateEditor({
plugins: [
ParagraphPlugin.configure({ component: ParagraphElement }),
CodeBlockPlugin.configure({ component: CodeBlockElement }),
BoldPlugin,
/* ... */
],
});import { createPlateEditor, ParagraphPlugin, PlateLeaf } from 'platejs/react';
import { BoldPlugin } from '@platejs/basic-nodes/react';
import { CodeBlockPlugin } from '@platejs/code-block/react';
import { ParagraphElement } from '@/components/editor/paragraph'; // Example UI component
import { CodeBlockElement } from '@/components/editor/code-block'; // Example UI component
const editor = createPlateEditor({
plugins: [
ParagraphPlugin.configure({ component: ParagraphElement }),
CodeBlockPlugin.configure({ component: CodeBlockElement }),
BoldPlugin,
/* ... */
],
});Refer to Plugin Components for more on creating/registering components.
For a react-markdown-like component for read-only display:
import React, { useEffect } from 'react';
import { Plate, PlateContent, usePlateEditor } from 'platejs/react';
import { MarkdownPlugin } from '@platejs/markdown';
// Import necessary Plate plugins for common Markdown features
import { BasicBlocksKit } from '@/components/editor/basic-blocks';
// ... include other plugins like BlockquotePlugin, CodeBlockPlugin, ListPlugin, etc.
// ... and mark plugins like BoldPlugin, ItalicPlugin, etc.
export interface PlateMarkdownProps {
children: string; // Markdown content
components?: Record<string, React.ComponentType<any>>; // Plate component overrides
className?: string;
}
export function PlateMarkdown({
children,
components = {},
className,
}: PlateMarkdownProps) {
const editor = usePlateEditor({
plugins: [
// Include all plugins needed to render your Markdown
...BasicBlocksKit,
MarkdownPlugin,
],
components, // Pass through component overrides
});
useEffect(() => {
editor.update((tx) => {
tx.value.replace(editor.api.markdown.deserialize(children));
});
}, [children, editor]); // Re-deserialize when the Markdown changes
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>import React, { useEffect } from 'react';
import { Plate, PlateContent, usePlateEditor } from 'platejs/react';
import { MarkdownPlugin } from '@platejs/markdown';
// Import necessary Plate plugins for common Markdown features
import { BasicBlocksKit } from '@/components/editor/basic-blocks';
// ... include other plugins like BlockquotePlugin, CodeBlockPlugin, ListPlugin, etc.
// ... and mark plugins like BoldPlugin, ItalicPlugin, etc.
export interface PlateMarkdownProps {
children: string; // Markdown content
components?: Record<string, React.ComponentType<any>>; // Plate component overrides
className?: string;
}
This PlateMarkdown component provides a read-only view. For full
editing, see the Installation guides.
@platejs/markdown prioritizes safety by converting Markdown to a structured Plate format, avoiding direct HTML rendering. However, security depends on:
remarkPlugins: Vet third-party remark plugins for potential security risks.LinkPlugin (isUrl) or MediaEmbedPlugin (parseMediaUrl) is crucial.Recommendation: Treat untrusted Markdown input cautiously. Sanitize if allowing complex features or raw HTML.
import { createPlateEditor } from 'platejs/react';
import {
MarkdownPlugin,
remarkMention,
remarkMdx,
} from '@platejs/markdown';
import remarkEmoji from 'remark-emoji';
import remarkGfm from 'remark-gfm';
import remarkMath from 'remark-math';
const editor = createPlateEditor({
plugins: [
// ...other Plate plugins
MarkdownPlugin.configure({
initialState: {
remarkPlugins: [
remarkMath,
remarkGfm,
remarkEmoji,
remarkMdx,
remarkMention,
],
},
}),
],
});import { createPlateEditor } from 'platejs/react';
import { MarkdownPlugin } from '@platejs/markdown';
import { ItalicPlugin } from '@platejs/basic-nodes/react';
import { BasicBlocksKit } from '@/components/editor/basic-blocks';
// ... import other necessary Plate plugins for rendering elements
const markdownString = '# Hello, *Plate*!';
const editor = createPlateEditor({
plugins: [
// MarkdownPlugin must be included
MarkdownPlugin,
...BasicBlocksKit,
ItalicPlugin,
],
// Use deserialize in the value factory for initial content
initialValue: ({ editor }) =>
editor.api.markdown.deserialize(markdownString),
});const specificNodes = [
{
children: [{ text: 'Serialize just this paragraph.' }],
type: 'paragraph',
},
{
children: [{ text: 'And this heading.' }],
type: 'heading', level: 1,
},
];
// Assuming `editor` is your Plate editor instance
const partialMarkdownOutput = editor.api.markdown.serialize({
value: { children: specificNodes },
});
console.info(partialMarkdownOutput);import { defineBasePlugin } from '@platejs/core';
import { property } from '@platejs/plite';
export const BaseDatePlugin = defineBasePlugin('date', {
codecs: ({ defineCodecs, schema: { type } }) =>
defineCodecs({
'text/html': {
decode: ({ element }) => ({ date: element.dataset.date }),
encode: ({ node }) => ({
attributes: { 'data-date': node.date },
tag: 'span',
}),
match: [{ attributes: { 'data-date': true }, tag: 'span' }],
},
'text/markdown': {
decode: ({ node, parseAttributes }) => ({
children: [{ text: '' }],
date: String(parseAttributes(node.attributes).value ?? ''),
type,
}),
encode: ({ node, propsToAttributes }) => ({
attributes: propsToAttributes({ value: node.date }),
children: [],
name: 'date',
type: 'mdxJsxTextElement',
}),
from: 'date',
kind: 'node',
},
}),
schema: {
element: {
properties: { date: property.string() },
void: 'inline',
},
},
});const markdown = `
A table:
| a | b |
| - | - |
~~Strikethrough~~
- [x] Task list item
Visit https://platejs.org
`;
// Assuming `editor` is your configured Plate editor instance
const document = editor.api.markdown.deserialize(markdown);
editor.update((tx) => {
tx.value.replace(document);
});
const markdownOutput = editor.api.markdown.serialize();
// markdownOutput will contain GFM syntaximport { createPlateEditor } from 'platejs/react';
import {
CodeBlockPlugin,
CodeHighlightPlugin,
CodeLinePlugin,
} from '@platejs/code-block/react';
import { MarkdownPlugin } from '@platejs/markdown';
import { MyCustomCodeBlockElement } from './my-custom-code-block'; // Your custom component
const editor = createPlateEditor({
plugins: [
CodeBlockPlugin.configure({ component: MyCustomCodeBlockElement }),
CodeLinePlugin.configure({ component: MyCustomCodeLineElement }),
CodeHighlightPlugin.configure({ component: MyCustomCodeSyntaxElement }),
MarkdownPlugin,
// ... other plugins
],
});
// MyCustomCodeBlockElement.tsx would then implement the desired rendering
// (e.g., using react-syntax-highlighter), consuming props from PlateElement.import { CodeBlockPlugin } from '@platejs/code-block/react';
const codeBlock = editor.plugin(CodeBlockPlugin);
const value = editor.api.markdown.deserialize(markdown, {
rules: {
[codeBlock.name]: {
deserialize: (mdastNode) => ({
children: [{ text: '' }],
language: mdastNode.lang ?? undefined,
rawCode: mdastNode.value || '',
type: codeBlock.schema.type,
}),
},
},
});
const output = editor.api.markdown.serialize({
value,
rules: {
[codeBlock.schema.type]: {
serialize: (node) => ({
lang: node.language,
type: 'code',
value: node.rawCode,
}),
},
},
});import { createPlateEditor } from 'platejs/react';
import { MarkdownPlugin } from '@platejs/markdown';
import remarkMath from 'remark-math';
// Import Plate math plugins for rendering
import {
EquationPlugin,
InlineEquationPlugin,
} from '@platejs/math/react';
const editor = createPlateEditor({
plugins: [
// ...other plugins
EquationPlugin,
InlineEquationPlugin,
MarkdownPlugin.configure({
initialState: {
remarkPlugins: [remarkMath],
// The installed math plugins own the `math` and `inlineMath` codecs.
},
}),
],
});import { createPlateEditor } from 'platejs/react';
import {
MarkdownPlugin,
remarkMention,
} from '@platejs/markdown';
import { MentionPlugin } from '@platejs/mention/react';
const editor = createPlateEditor({
plugins: [
// ...other plugins
MentionPlugin,
MarkdownPlugin.configure({
initialState: {
remarkPlugins: [remarkMention],
},
}),
],
});import { createPlateEditor } from 'platejs/react';
import {
MarkdownPlugin,
remarkMdx,
} from '@platejs/markdown';
import { ColumnPlugin, ColumnItemPlugin } from '@platejs/layout/react';
const editor = createPlateEditor({
plugins: [
// ...other plugins
ColumnPlugin,
ColumnItemPlugin,
MarkdownPlugin.configure({
initialState: {
remarkPlugins: [remarkMdx], // Required for column MDX syntax
},
}),
],
});const markdown = `
<columnGroup>
<column width="50%">
Left column content with 50% width
</column>
<column width="50%">
Right column content with 50% width
</column>
</columnGroup>
<columnGroup>
<column width="33%">First</column>
<column width="33%">Second</column>
<column width="34%">Third</column>
</columnGroup>
`;
// Assuming `editor` is your configured Plate editor instance
const value = editor.api.markdown.deserialize(markdown);
// Creates a columnGroup node with nested column elements
const markdownOutput = editor.api.markdown.serialize({ value });
// Preserves column structure with width attributes