Skip to main content

Overview

The @lexical/markdown package provides bidirectional conversion between Lexical editor state and Markdown, plus live Markdown shortcuts during editing.

Installation

npm install @lexical/markdown
This package requires @lexical/code, @lexical/link, @lexical/list, and @lexical/rich-text for full functionality.

Core Functions

$convertFromMarkdownString

Converts Markdown string to Lexical nodes.
function $convertFromMarkdownString(
  markdown: string,
  transformers?: Array<Transformer>,
  node?: ElementNode,
  shouldPreserveNewLines?: boolean,
  shouldMergeAdjacentLines?: boolean
): void
markdown
string
required
The Markdown string to convert
transformers
Transformer[]
default:"TRANSFORMERS"
Custom transformers to use for conversion
node
ElementNode
Target node to append content to (defaults to root)
shouldPreserveNewLines
boolean
default:"false"
Whether to preserve line breaks in conversion
shouldMergeAdjacentLines
boolean
default:"false"
Whether to merge adjacent non-empty lines per CommonMark spec
Example:
import { $convertFromMarkdownString, TRANSFORMERS } from '@lexical/markdown';

editor.update(() => {
  const markdown = `# Hello World\n\nThis is **bold** text.`;
  $convertFromMarkdownString(markdown, TRANSFORMERS);
});

$convertToMarkdownString

Converts Lexical editor state to Markdown string.
function $convertToMarkdownString(
  transformers?: Array<Transformer>,
  node?: ElementNode,
  shouldPreserveNewLines?: boolean
): string
transformers
Transformer[]
default:"TRANSFORMERS"
Custom transformers to use for conversion
node
ElementNode
Source node to convert (defaults to root)
shouldPreserveNewLines
boolean
default:"false"
Whether to preserve line breaks in output
Returns: Markdown string Example:
import { $convertToMarkdownString, TRANSFORMERS } from '@lexical/markdown';

editor.getEditorState().read(() => {
  const markdown = $convertToMarkdownString(TRANSFORMERS);
  console.log(markdown);
});

Markdown Shortcuts

registerMarkdownShortcuts

Registers live Markdown shortcuts (e.g., typing **text** creates bold).
function registerMarkdownShortcuts(
  editor: LexicalEditor,
  transformers: Array<Transformer>
): () => void
editor
LexicalEditor
required
The editor instance
transformers
Transformer[]
required
Transformers to enable as shortcuts
Returns: Cleanup function Example:
import { registerMarkdownShortcuts, TRANSFORMERS } from '@lexical/markdown';

const unregister = registerMarkdownShortcuts(editor, TRANSFORMERS);

// Now typing **bold** will create bold text
// Typing # at line start creates heading
// etc.

Built-in Transformers

Text Format Transformers

Transformers for inline text formatting:

BOLD_STAR

Bold text with **text** or __text__
const BOLD_STAR: TextFormatTransformer
const BOLD_UNDERSCORE: TextFormatTransformer

ITALIC_STAR

Italic text with *text* or _text_
const ITALIC_STAR: TextFormatTransformer
const ITALIC_UNDERSCORE: TextFormatTransformer

BOLD_ITALIC_STAR

Bold and italic with ***text*** or ___text___
const BOLD_ITALIC_STAR: TextFormatTransformer
const BOLD_ITALIC_UNDERSCORE: TextFormatTransformer

STRIKETHROUGH

Strikethrough with ~~text~~
const STRIKETHROUGH: TextFormatTransformer

HIGHLIGHT

Highlight with ==text==
const HIGHLIGHT: TextFormatTransformer

INLINE_CODE

Inline code with `code`
const INLINE_CODE: TextFormatTransformer

Text Match Transformers

Transformers for special patterns: Links with [text](url)
const LINK: TextMatchTransformer

Element Transformers

Transformers for block elements:

HEADING

Headings with #, ##, etc.
const HEADING: ElementTransformer
Supports: h1 through h6

QUOTE

Block quotes with >
const QUOTE: ElementTransformer

ORDERED_LIST

Ordered lists with 1., 2., etc.
const ORDERED_LIST: ElementTransformer

UNORDERED_LIST

Unordered lists with -, *, or +
const UNORDERED_LIST: ElementTransformer

CHECK_LIST

Check lists with - [ ] or - [x]
const CHECK_LIST: ElementTransformer

Multiline Element Transformers

CODE

Code blocks with triple backticks
const CODE: MultilineElementTransformer
Supports: Language specifier (```javascript)

Transformer Collections

TRANSFORMERS

Complete set of all transformers.
const TRANSFORMERS: Array<Transformer>
Includes all text format, text match, element, and multiline transformers.

TEXT_FORMAT_TRANSFORMERS

All text formatting transformers.
const TEXT_FORMAT_TRANSFORMERS: Array<TextFormatTransformer>

TEXT_MATCH_TRANSFORMERS

All text match transformers (links).
const TEXT_MATCH_TRANSFORMERS: Array<TextMatchTransformer>

ELEMENT_TRANSFORMERS

All block element transformers.
const ELEMENT_TRANSFORMERS: Array<ElementTransformer>

MULTILINE_ELEMENT_TRANSFORMERS

All multiline transformers (code blocks).
const MULTILINE_ELEMENT_TRANSFORMERS: Array<MultilineElementTransformer>

Custom Transformers

You can create custom transformers for specific patterns:

TextFormatTransformer

type TextFormatTransformer = {
  format: TextFormatType[];
  tag: string;
  intraword?: boolean;
  type: 'text-format';
}

TextMatchTransformer

type TextMatchTransformer = {
  dependencies: Array<Class<LexicalNode>>;
  export: (node: LexicalNode, exportChildren: (node: ElementNode) => string, exportFormat: (node: TextNode) => string) => string | null;
  importRegExp: RegExp;
  regExp: RegExp;
  replace: (textNode: TextNode, match: RegExpMatchArray) => void;
  trigger: string;
  type: 'text-match';
}

ElementTransformer

type ElementTransformer = {
  dependencies: Array<Class<LexicalNode>>;
  export: (node: LexicalNode, exportChildren: (node: ElementNode) => string) => string | null;
  regExp: RegExp;
  replace: (parentNode: ElementNode, children: Array<LexicalNode>, match: RegExpMatchArray, isImport: boolean) => void;
  type: 'element';
}

MultilineElementTransformer

type MultilineElementTransformer = {
  dependencies: Array<Class<LexicalNode>>;
  export: (node: LexicalNode, exportChildren: (node: ElementNode) => string) => string | null;
  regExpEnd: RegExp;
  regExpStart: RegExp;
  replace: (rootNode: ElementNode, children: Array<LexicalNode>, startMatch: RegExpMatchArray, endMatch: RegExpMatchArray, isImport: boolean) => void;
  type: 'multiline-element';
}

Complete Example

import { createEditor } from 'lexical';
import {
  $convertFromMarkdownString,
  $convertToMarkdownString,
  registerMarkdownShortcuts,
  TRANSFORMERS
} from '@lexical/markdown';
import { HeadingNode, QuoteNode } from '@lexical/rich-text';
import { CodeNode } from '@lexical/code';
import { LinkNode } from '@lexical/link';
import { ListNode, ListItemNode } from '@lexical/list';

const editor = createEditor({
  nodes: [
    HeadingNode,
    QuoteNode,
    CodeNode,
    LinkNode,
    ListNode,
    ListItemNode
  ]
});

editor.setRootElement(document.getElementById('editor'));

// Enable Markdown shortcuts
const unregisterShortcuts = registerMarkdownShortcuts(editor, TRANSFORMERS);

// Import Markdown
const markdown = `
# My Document

This is **bold** and *italic* text.

- List item 1
- List item 2

\`\`\`javascript
const x = 42;
\`\`\`
`;

editor.update(() => {
  $convertFromMarkdownString(markdown, TRANSFORMERS);
});

// Export to Markdown
function exportMarkdown() {
  editor.getEditorState().read(() => {
    const markdown = $convertToMarkdownString(TRANSFORMERS);
    console.log(markdown);
    return markdown;
  });
}

// Later, cleanup
unregisterShortcuts();

Custom Transformer Example

import type { TextFormatTransformer } from '@lexical/markdown';

// Custom transformer for underline with __text__
const UNDERLINE: TextFormatTransformer = {
  format: ['underline'],
  tag: '__',
  type: 'text-format'
};

// Use with custom transformer set
const customTransformers = [
  ...TRANSFORMERS,
  UNDERLINE
];

registerMarkdownShortcuts(editor, customTransformers);

Build docs developers (and LLMs) love