mrkeyoor.com_
Wed 23 Sept 00:35 UTC
npmWeb Frontendupdated 22 Sept 2026

@lexical/markdown review

@lexical/markdown 0.49.0 translates between Markdown text and a Lexical editor tree, and it can turn typed markers into rich editor nodes. You supply a transformer list that defines accepted blocks and inline syntax, including headings, quotes, lists, fenced code, links, and text formats. The 0.49 release fixes a case where typing a list marker at the start of a heading incorrectly replaced that heading with a list. This is a Lexical adapter rather than a standalone Markdown parser: our full-namespace browser build reached 358.2 KB minified and 116.3 KB gzipped because it pulls in the editor stack.

Verdict

@lexical/markdown 0.49.0 installed 16 packages in 8.4 seconds and made our namespace browser build 116.3 KB gzipped, so it earns its place only when Markdown must cross a Lexical editor boundary. For parsing or read-only rendering, install a standalone Markdown tool instead.

We installed it

Lab card: what happened when we installed @lexical/markdownScreenshot of @lexical/markdown documentation
Install✓ · 8.4s16 packages on disk · 9 MB
ImportESM import works · require() works · CommonJS package with exports map
Browser116.3 KBgzipped (358.2 KB minified), bundled with esbuild
TypesTypeScript types bundled
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does @lexical/markdown install cleanly?

Yes. In a fresh container with an empty cache, npm install @lexical/markdown finished in 8 seconds, leaving 16 packages and 9 MB on disk. npm audit reported no known vulnerabilities.

How much does @lexical/markdown add to a browser bundle?

116.3 KB gzipped (358.2 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.

Does @lexical/markdown work with both ESM and CommonJS?

Yes. Both import '@lexical/markdown' and require('@lexical/markdown') worked in Node 22 in our run. The package is published as CommonJS with an exports map.

Does @lexical/markdown include TypeScript types?

Yes, type declarations ship inside the package, so no @types install is needed.

@lexical/markdown or markdown-it: which should you use?

markdown-it: Use it for extensible Markdown-to-HTML rendering when no Lexical editor tree is involved. @lexical/markdown 0.49.0 installed 16 packages in 8.4 seconds and made our namespace browser build 116.3 KB gzipped, so it earns its place only when Markdown must cross a Lexical editor boundary.

When should you not use @lexical/markdown?

You need Markdown to HTML or a syntax tree without an editor. This package has 9 direct dependencies tied to Lexical, while marked, markdown-it, or remark-parse handles that job directly.

API stability3/5The public surface is compact: conversion helpers, a shortcut registrar, transformer types, and named transformer constants. Still, every dependency in 0.49.0 is pinned to the matching Lexical version, and the family remains below 1.0. Recent releases added selection export, generated-node import, Enter-triggered block shortcuts, and composition-trigger behavior. Those changes solve real editor cases, but they also mean mixed versions and untested upgrades can alter typing or serialization behavior.
Docs4/5The package README covers string import, whole-state export, LexicalComposer initialization, React shortcuts, framework-free registration, and transformer groups. The API site lists each exported transformer and function with its types. Setup remains fragmented: examples do not put all required node classes beside TRANSFORMERS, even though the registrar verifies node dependencies at runtime. Developers must combine the Markdown page with Lexical node and editor-state concepts to get a safe first integration.
Maintenance5/5GitHub shows 23,793 stars, 336 open issues and pull requests, an unarchived repository, and a push on August 25, 2026. Version 0.49.0 shipped on July 30 as part of Lexical's coordinated monthly release. Its Markdown-specific fix preserves headings when list shortcuts are typed, while previous releases addressed hard line breaks, Unicode whitespace, selection export, and shortcut triggers. The queue is large, yet the package is maintained inside the active parent editor rather than as a detached bridge.
Ecosystem4/5npm recorded 4,782,008 downloads in the latest completed week. The module connects directly to Lexical's link, list, text, rich-text, selection, code, React, and extension packages, and it also exposes a registration API for non-React editors. That makes it useful throughout the Lexical ecosystem. Its transformer objects are specific to Lexical, however, so remark plugins and general mdast tools cannot be dropped into the same pipeline without a separate conversion layer.

Use it if

  • A Lexical editor must import existing Markdown and serialize edited content back to a text format.
  • Writers should get heading, quote, list, code, link, and emphasis shortcuts while typing in a Lexical surface.
  • The product needs an explicit allowlist of Markdown features instead of accepting every syntax extension.
  • Copy or export flows need Markdown for only the current Lexical selection.
Skip it if

Setup reality

We installed @lexical/markdown 0.49.0 in a fresh Node 22 Bookworm sandbox. npm took 8.4 seconds, leaving 16 packages and 9 MB on disk. The package itself is 456 KB unpacked, declares 9 direct dependencies and 1 peer dependency, and returned 0 npm audit findings. It is published as CommonJS with an exports map, bundled TypeScript declarations, and working require() and ESM import paths. Our namespace browser build measured 358.2 KB minified and 116.3 KB gzipped.

There are no credentials or config files. The setup cost is node registration: headings and quotes need rich-text nodes, lists need ListNode and ListItemNode, links need LinkNode, and fenced code needs CodeNode. registerMarkdownShortcuts checks required nodes and can throw during setup. Keep every Lexical package on 0.49.0, register the nodes before conversion, and use one transformer array for import, shortcuts, and export.

The conversion helpers start with $, so call them inside editor.update(), an editor-state read, or LexicalComposer's editorState initializer. $convertFromMarkdownString clears its target root by default and repositions selection. $generateNodesFromMarkdownString is safer when inserting into an existing document because it returns detached nodes. Non-React integrations must retain and call the cleanup function returned by registerMarkdownShortcuts.

TRANSFORMERS is a product choice, not full Markdown compatibility. It includes a highlight syntax written as ==text==, excludes the separately exported CHECK_LIST, and has no built-in table or image transformer. Version 0.49.0 fixes the heading-to-list shortcut bug, but Markdown still will not preserve every source token. Test import followed by export on real documents before treating the text as canonical storage.

Patterns

Keep Markdown and core Lexical on one release install-matched-packages

npm install lexical@0.49.0 @lexical/markdown@0.49.0

@lexical/markdown 0.49.0 declares exact 0.49.0 runtime dependencies. Do not mix its version with another Lexical line.

Load Markdown into the editor root replace-editor-from-markdown

import { $convertFromMarkdownString, TRANSFORMERS } from '@lexical/markdown';

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

The call clears the target root by default. Run it inside editor.update() or the editor-state initializer.

Read the editor state as Markdown serialize-editor-to-markdown

import { $convertToMarkdownString, TRANSFORMERS } from '@lexical/markdown';

const markdown = editor.getEditorState().read(() =>
  $convertToMarkdownString(TRANSFORMERS),
);

Use the same transformer array used for import. A missing exporter can turn a rich node into plain text or omit its structure.

Create a React editor from Markdown initialize-composer-from-markdown

const initialConfig = {
  namespace: 'Article',
  nodes: [HeadingNode, QuoteNode, ListNode, ListItemNode, LinkNode, CodeNode],
  onError(error) { throw error; },
  editorState: () => $convertFromMarkdownString(markdown, TRANSFORMERS),
};

<LexicalComposer initialConfig={initialConfig}>{children}</LexicalComposer>

All 6 node classes shown support constructs in TRANSFORMERS. Register them before the initializer converts content.

Turn typed markers into rich nodes enable-react-shortcuts

import { TRANSFORMERS } from '@lexical/markdown';
import { MarkdownShortcutPlugin } from '@lexical/react/LexicalMarkdownShortcutPlugin';

<MarkdownShortcutPlugin transformers={TRANSFORMERS} />

The plugin changes editor nodes as the user types. Lexical state remains a node tree rather than a Markdown string.

Install shortcuts without React register-vanilla-shortcuts

const removeShortcuts = registerMarkdownShortcuts(editor, TRANSFORMERS);

// during teardown
removeShortcuts();

The returned function removes listeners and commands. Call it when the editor integration is disposed.

Limit authors to selected syntax allow-markdown-subset

import { BOLD_STAR, HEADING, ITALIC_STAR, LINK, QUOTE } from '@lexical/markdown';

const ARTICLE_TRANSFORMERS = [
  HEADING, QUOTE, BOLD_STAR, ITALIC_STAR, LINK,
];

Transformer order affects overlapping inline markers. Preserve the package ordering for formats that can share delimiters.

Include task-list conversion explicitly add-check-list-syntax

import { CHECK_LIST, TRANSFORMERS } from '@lexical/markdown';

const transformers = [CHECK_LIST, ...TRANSFORMERS];

CHECK_LIST is exported but absent from TRANSFORMERS. Register ListNode and ListItemNode before using it.

Keep each source newline during import preserve-import-newlines

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

The fourth argument preserves newlines and changes how adjacent text lines become editor blocks.

Merge adjacent text lines merge-commonmark-lines

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

The fifth argument merges adjacent lines only when preserveNewLines is false. Test hard breaks in your own documents.

Insert parsed nodes at the selection insert-markdown-fragment

editor.update(() => {
  const selection = $getSelection();
  const nodes = $generateNodesFromMarkdownString(markdown, TRANSFORMERS);
  selection?.insertNodes(nodes);
});

$generateNodesFromMarkdownString returns detached nodes and does not clear the existing root.

Copy only selected content as Markdown export-current-selection

const text = editor.getEditorState().read(() =>
  $convertSelectionToMarkdownString(TRANSFORMERS, $getSelection()),
);

A missing or collapsed selection yields an empty string rather than exporting the whole editor.

Alternatives

PackageRegistryPick it when
markdown-itnpmUse it for extensible Markdown-to-HTML rendering when no Lexical editor tree is involved.
markednpmUse it for a direct lexer and renderer with a small standalone API.
remark-parsenpmUse it when Markdown should become a mdast tree processed by unified plugins.
react-markdownnpmUse it to render Markdown as React elements without adding an editable Lexical document model.

More web frontend guides

postcss · react · react-dom · tailwindcss · htmlparser2 · tailwind-merge · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.