payloadcms / payloadcms/payload
Pasting HTML containing `<hr>` into Lexical editor throws `Type horizontalrule in node X does not match registered node Y`
@AlessioGr is already working on this.
Since May 6, 2026.
- Dominant language
- TypeScript
- Stars
- 44.8k
- Forks
- 4.2k
- Avg merge
- 2d 21h
- Merged PRs (30d)
- 53
Description
Describe the Bug
Pasting any HTML that contains a horizontal rule (<hr>) into a Payload v3 Lexical editor throws a class-identity mismatch error and the paste is silently dropped from the editor state. Reproduces with the default lexicalEditor() config — no custom features required. Triggered most easily by pasting from a Microsoft Word document that contains a horizontal rule, but any clipboard payload containing <hr> reproduces it (raw HTML pasted via a clipboard tool, copying a rendered web page with an <hr>, etc.).
HorizontalRuleFeature is included by default in defaultFeatures for lexicalEditor() — confirmed by inspecting the auto-generated import map, which references '@payloadcms/richtext-lexical/client#HorizontalRuleFeatureClient'.
Stack trace
Error: Create node: Type horizontalrule in node St does not match registered node ke with the same type
at formatDevErrorMessage (lexical/Lexical.dev.mjs:465:11)
at errorOnTypeKlassMismatch (lexical/Lexical.dev.mjs:4439:13)
at new LexicalNode (lexical/Lexical.dev.mjs:3662:17)
at new DecoratorNode (lexical/Lexical.dev.mjs:9687:1)
at new St (@payloadcms/richtext-lexical/dist/exports/client/...:5210:267)
at Wl (@payloadcms/richtext-lexical/dist/exports/client/...:5263:237)
at wm (@payloadcms/richtext-lexical/dist/exports/client/...:5259:15)
at $createNodesFromDOM (@lexical/clipboard:18812:49)
at $createNodesFromDOM (@lexical/clipboard:18841:35)
at $createNodesFromDOM (@lexical/clipboard:18841:35)
at $createNodesFromDOM (@lexical/clipboard:18841:35)
at $generateNodesFromDOM (@lexical/clipboard:18715:33)
at $insertDataTransferForRichText (@lexical/clipboard:19060:275)
at editor.update.tag (@lexical/clipboard:19860:285)
at $processNestedUpdates (lexical/Lexical.dev.mjs:8753:17)
at $beginUpdate (lexical/Lexical.dev.mjs:8803:26)
at updateEditorSync (lexical/Lexical.dev.mjs:8881:9)
at triggerCommandListeners (lexical/Lexical.dev.mjs:8681:21)
at dispatchCommand (lexical/Lexical.dev.mjs:11734:12)
at HTMLDivElement.eventHandler (lexical/Lexical.dev.mjs:3327:42)
(Class names are minified — St is Payload's HorizontalRuleNode, ke is the bare Lexical HorizontalRuleNode.)
Root cause analysis
errorOnTypeKlassMismatch (lexical/Lexical.dev.mjs:4439) requires that the constructor of any new node match the class registered for that node's type string in the editor's node registry:
function errorOnTypeKlassMismatch(type, klass) {
const registeredNode = getRegisteredNode(getActiveEditor(), type);
if (registeredNode === undefined) { /* ... */ }
const editorKlass = registeredNode.klass;
if (editorKlass !== klass) {
formatDevErrorMessage(
`Create node: Type ${type} in node ${klass.name} does not match ` +
`registered node ${editorKlass.name} with the same type`,
);
}
}
When the paste path runs $createNodesFromDOM, Lexical's bare DOM importer (from @lexical/clipboard / @lexical/utils) instantiates the HorizontalRuleNode exported from @lexical/react/LexicalHorizontalRuleNode. But the editor's registry holds Payload's own HorizontalRuleNode subclass, exported from @payloadcms/richtext-lexical/dist/exports/client. Two different class identities for the same type: 'horizontalrule' string → mismatch → throw.
This is a class-identity collision caused by Payload re-implementing/wrapping HorizontalRuleNode in its own bundle while the upstream Lexical paste path imports the bare class. It is not a duplicate-package issue (pnpm why @payloadcms/richtext-lexical shows a single declared version, and apps/cms/node_modules/@payloadcms/richtext-lexical resolves to a single virtual store).
Why <hr> is special
Most Word/HTML pastes work — headings, links, emphasis, lists all come through the same $createNodesFromDOM path without crashing. The crash is specific to <hr> because Payload's HorizontalRuleFeature registers a subclass of Lexical's HorizontalRuleNode rather than the original class. Other built-in nodes (Heading, List, Link, Text) appear to use the upstream classes directly, so the registry and the paste-path importer agree on identity.
Suggested fix directions
- Have Payload's
HorizontalRuleFeatureregister the upstreamHorizontalRuleNodeclass directly instead of subclassing it (if subclassing was for a small augmentation, move the augmentation to a separate concern). - Or: register the subclass with a different
typestring (e.g.'payload-horizontalrule') and add a DOM converter that emits the new type for<hr>elements. - Or: extend the editor's node registry such that subclasses with the same
typestring are accepted as long as one is aprototypeof the other — a Lexical-side change, more invasive.
Workaround
For end users: strip horizontal rules from the source document before pasting. There is no in-editor mitigation today; the upstream paste handler runs at COMMAND_PRIORITY_EDITOR and a custom PASTE_COMMAND handler at higher priority still cannot prevent the failing path from running on the original event without consuming or re-dispatching, both of which require touching Payload's node registry directly.
Link to the code that reproduces this issue
https://github.com/adamslowe/payload-lexical-hr-bug
Reproduction Steps
- Scaffold a Payload v3.82.1 + Next.js 16 project from the blank template:
pnpx create-payload-app@latest -t blank - Confirm the editor is the default Lexical setup with no custom features:
// payload.config.ts import { lexicalEditor } from '@payloadcms/richtext-lexical' export default buildConfig({ editor: lexicalEditor(), // ... }) - Create or use any existing collection with a
richTextfield. - In Microsoft Word, type a paragraph, insert a horizontal rule (type
---and press Enter, or use Insert → Horizontal Line), then a second paragraph. (Or use the `Demo-Document.docx from the repo) - Copy the entire selection (Ctrl/Cmd+A, Ctrl/Cmd+C).
- Open the rich-text field in the Payload admin, click into it, and paste.
- Open browser DevTools → Console.
Expected: the content pastes — with the horizontal rule converted to a HorizontalRuleNode (or, if Payload prefers, dropped silently without an error).
Actual: the browser console shows
Error: Create node: Type horizontalrule in node St does not match registered node ke with the same type
The editor does not visually crash, but the pasted content is partially or wholly dropped from the editor state.
Also reproduces with non-Word sources: copying a rendered web page that contains an <hr>, or pasting raw HTML through a clipboard tool, both trigger the same error. (https://gist.github.com/jaredpalmer/2f37ab6d57b6bc88719a9272a3799bee)
Which area(s) are affected?
plugin: richtext-lexical
Environment Info
Binaries:
Node: 22.22.2
npm: 10.9.7
Yarn: N/A
pnpm: 10.33.2
Relevant Packages:
payload: 3.84.1
next: 16.2.3
@payloadcms/db-sqlite: 3.84.1
@payloadcms/drizzle: 3.84.1
@payloadcms/graphql: 3.84.1
@payloadcms/live-preview: 3.84.1
@payloadcms/live-preview-react: 3.84.1
@payloadcms/next/utilities: 3.84.1
@payloadcms/plugin-form-builder: 3.84.1
@payloadcms/plugin-nested-docs: 3.84.1
@payloadcms/plugin-redirects: 3.84.1
@payloadcms/plugin-search: 3.84.1
@payloadcms/plugin-seo: 3.84.1
@payloadcms/richtext-lexical: 3.84.1
@payloadcms/translations: 3.84.1
@payloadcms/ui/shared: 3.84.1
react: 19.2.4
react-dom: 19.2.4
Operating System:
Platform: linux
Arch: x64
Version: #15-Ubuntu SMP PREEMPT_DYNAMIC Wed Apr 22 16:06:43 UTC 2026
Available memory (MB): 30967
Available CPU cores: 8
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.