<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: dragogargo</title>
    <description>The latest articles on DEV Community by dragogargo (@dragogargo).</description>
    <link>https://dev.to/dragogargo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3830138%2F7a038a1e-0635-4d3a-8a75-93c8f87afeb9.png</url>
      <title>DEV Community: dragogargo</title>
      <link>https://dev.to/dragogargo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dragogargo"/>
    <language>en</language>
    <item>
      <title>How to Migrate from Draft.js to Lexical in 2026 (Complete Guide)</title>
      <dc:creator>dragogargo</dc:creator>
      <pubDate>Tue, 17 Mar 2026 22:54:26 +0000</pubDate>
      <link>https://dev.to/dragogargo/how-to-migrate-from-draftjs-to-lexical-in-2026-complete-guide-4960</link>
      <guid>https://dev.to/dragogargo/how-to-migrate-from-draftjs-to-lexical-in-2026-complete-guide-4960</guid>
      <description>&lt;p&gt;Draft.js is archived. No maintenance, no security patches, 820K weekly downloads running on momentum. Meta says use Lexical, but never shipped a migration path — issues #1641 and #2197 have been open since 2022.&lt;/p&gt;

&lt;p&gt;I built draft-to-lexical because I needed it and it didn't exist.&lt;/p&gt;

&lt;p&gt;The problem&lt;br&gt;
Draft.js content (RawDraftContentState) is a flat list of blocks with style ranges and an entity map. Lexical (SerializedEditorState) is a nested node tree. You can't rename a few fields and call it done — the structures are incompatible.&lt;/p&gt;

&lt;p&gt;Draft.js:&lt;/p&gt;

&lt;p&gt;{&lt;br&gt;
  "blocks": [&lt;br&gt;
    {&lt;br&gt;
      "text": "Hello bold world",&lt;br&gt;
      "type": "unstyled",&lt;br&gt;
      "inlineStyleRanges": [&lt;br&gt;
        { "offset": 6, "length": 4, "style": "BOLD" }&lt;br&gt;
      ]&lt;br&gt;
    }&lt;br&gt;
  ],&lt;br&gt;
  "entityMap": {}&lt;br&gt;
}&lt;br&gt;
Lexical:&lt;/p&gt;

&lt;p&gt;{&lt;br&gt;
  "root": {&lt;br&gt;
    "type": "root",&lt;br&gt;
    "children": [&lt;br&gt;
      {&lt;br&gt;
        "type": "paragraph",&lt;br&gt;
        "children": [&lt;br&gt;
          { "type": "text", "text": "Hello ", "format": 0 },&lt;br&gt;
          { "type": "text", "text": "bold", "format": 1 },&lt;br&gt;
          { "type": "text", "text": " world", "format": 0 }&lt;br&gt;
        ]&lt;br&gt;
      }&lt;br&gt;
    ]&lt;br&gt;
  }&lt;br&gt;
}&lt;br&gt;
The differences that matter:&lt;/p&gt;

&lt;p&gt;Flat vs. nested: Draft.js blocks are a flat array. Lexical nodes form a tree.&lt;br&gt;
Style ranges vs. format flags: Draft.js uses offset/length ranges. Lexical uses bitwise flags on each text node.&lt;br&gt;
Entity map vs. wrapper nodes: Draft.js stores links in a separate entityMap. Lexical wraps text inside LinkNode.&lt;br&gt;
List items: Draft.js keeps them as flat blocks with a depth field. Lexical nests listitem nodes inside list nodes.&lt;br&gt;
Install&lt;/p&gt;

&lt;p&gt;npm install draft-to-lexical&lt;br&gt;
As a library&lt;/p&gt;

&lt;p&gt;import { convert } from 'draft-to-lexical';&lt;/p&gt;

&lt;p&gt;const draftContent = {&lt;br&gt;
  blocks: [&lt;br&gt;
    {&lt;br&gt;
      key: 'a1',&lt;br&gt;
      text: 'Hello bold world',&lt;br&gt;
      type: 'unstyled',&lt;br&gt;
      depth: 0,&lt;br&gt;
      inlineStyleRanges: [{ offset: 6, length: 4, style: 'BOLD' }],&lt;br&gt;
      entityRanges: [],&lt;br&gt;
      data: {},&lt;br&gt;
    },&lt;br&gt;
  ],&lt;br&gt;
  entityMap: {},&lt;br&gt;
};&lt;/p&gt;

&lt;p&gt;const lexicalState = convert(draftContent);&lt;br&gt;
As a CLI&lt;/p&gt;

&lt;h1&gt;
  
  
  Single file
&lt;/h1&gt;

&lt;p&gt;draft-to-lexical convert input.json -o output.json --pretty&lt;/p&gt;

&lt;h1&gt;
  
  
  Pipe from stdin
&lt;/h1&gt;

&lt;p&gt;cat draft-content.json | draft-to-lexical convert --pretty&lt;/p&gt;

&lt;h1&gt;
  
  
  Batch convert a directory
&lt;/h1&gt;

&lt;p&gt;draft-to-lexical batch ./draft-files/ -o ./lexical-files/ --pretty&lt;br&gt;
What gets converted&lt;br&gt;
Block types&lt;br&gt;
Draft.js    Lexical&lt;br&gt;
unstyled    paragraph&lt;br&gt;
header-one ... header-six   heading (h1-h6)&lt;br&gt;
blockquote  quote&lt;br&gt;
code-block  code&lt;br&gt;
unordered-list-item listitem inside list (bullet)&lt;br&gt;
ordered-list-item   listitem inside list (number)&lt;br&gt;
atomic  Depends on entity&lt;br&gt;
Inline styles&lt;br&gt;
All standard styles map to Lexical's bitwise format flags: BOLD (1), ITALIC (2), UNDERLINE (4), STRIKETHROUGH (8), CODE (16), HIGHLIGHT (32), SUBSCRIPT (64), SUPERSCRIPT (128).&lt;/p&gt;

&lt;p&gt;Overlapping styles get OR'd together — bold + italic = format flag 3.&lt;/p&gt;

&lt;p&gt;Entities&lt;br&gt;
Links become Lexical LinkNode wrapping text children. Images become ImageNode with src, alt, width, height.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Custom&lt;/strong&gt; &lt;strong&gt;entities&lt;/strong&gt;&lt;br&gt;
Every Draft.js project has custom entities — mentions, embeds, file attachments. The converter lets you write handlers for those:&lt;/p&gt;

&lt;p&gt;import { convert } from 'draft-to-lexical';&lt;/p&gt;

&lt;p&gt;const result = convert(draftContent, {&lt;br&gt;
  entityHandlers: {&lt;br&gt;
    MENTION: (entity, text, children) =&amp;gt; ({&lt;br&gt;
      type: 'mention',&lt;br&gt;
      version: 1,&lt;br&gt;
      mentionName: entity.data.name,&lt;br&gt;
      detail: 0,&lt;br&gt;
      format: 0,&lt;br&gt;
      mode: 'normal',&lt;br&gt;
      style: '',&lt;br&gt;
      text,&lt;br&gt;
    }),&lt;br&gt;
    VIDEO: (entity) =&amp;gt; ({&lt;br&gt;
      type: 'video',&lt;br&gt;
      version: 1,&lt;br&gt;
      src: entity.data.src,&lt;br&gt;
    }),&lt;br&gt;
  },&lt;br&gt;
});&lt;br&gt;
Migrating a database&lt;br&gt;
If you have thousands of documents stored as Draft.js JSON, use the batch CLI or write a migration script:&lt;/p&gt;

&lt;p&gt;import { convert } from 'draft-to-lexical';&lt;br&gt;
import { db } from './your-database';&lt;/p&gt;

&lt;p&gt;const documents = await db.query('SELECT id, content FROM documents');&lt;/p&gt;

&lt;p&gt;for (const doc of documents) {&lt;br&gt;
  const draftContent = JSON.parse(doc.content);&lt;br&gt;
  const lexicalState = convert(draftContent);&lt;br&gt;
  await db.query(&lt;br&gt;
    'UPDATE documents SET content = $1 WHERE id = $2',&lt;br&gt;
    [JSON.stringify(lexicalState), doc.id]&lt;br&gt;
  );&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Links&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/dragogargo/draft-to-lexical" rel="noopener noreferrer"&gt;https://github.com/dragogargo/draft-to-lexical&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.npmjs.com/package/draft-to-lexical" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/draft-to-lexical&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>react</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
