Releases

RSS

Latest updates and announcements.

v54.0.0-beta.0

@platejs/core

Breaking Changes

  • Prepare v54 beta prerelease versioning. (#5031)

@platejs/plite

Breaking Changes

  • Prepare v54 beta prerelease versioning for Plite. (#5031)

CHANGELOG · v53.2.0...v54.0.0-beta.0 · By @felixfeng33

behavior
block-selectiontable

Select custom Plate block content from its schema and let tables render their own selection geometry.

behavior
plate-uitoolbarfloating-popovereditor-dropdown-menueditor-context-menu

Install Base UI source with `base-nova` by default. Explicit Radix styles select the matching interaction adapters.

behavior
plate-uitoolbarfloating-popover

Install Nova, Vega, Maia, Lyra, Mira, Luma, Sera, or Rhea code styles without duplicating the complete editor registry.

fix
block-menucode-blockcode-drawingcommentemoji-pickerfont-color-toolbar-buttonfont-size-toolbar-buttonfootnotemathtabletable-toolbar-buttonturn-into-toolbar-button

Compose popovers, dropdowns, and context menus through the selected provider without provider-specific trigger, focus, delay, or value props in consumer code.

fix
autoformat-classicfixed-toolbar-classicfixed-toolbar-classic-buttonsfloating-toolbar-classicfloating-toolbar-classic-buttonsinsert-toolbar-classic-buttonlist-classiclist-classic-toolbar-buttontransforms-classicturn-into-toolbar-classic-button

Install the classic editor graph with Base or Radix through the same semantic item ids.

fix
fumadocs

Install the docs block with Base or Radix without fixed Radix primitive packages.

fix
editor-basicaidatelinksuggestion-style

Create a fresh Next project with a complete dependency graph, collision-free inline-suggestion imports, and current copied-source types.

remove
editor-plugins

Install the authored editor kit without generated TypeScript or schema snapshots. Generate exact contracts from the application's customized plugin module and enforce `plate generate --check <entry>` in CI.

  • Delete unused plugins.generated.ts and plugins.schema.json files. If the app needs exact contracts, install @platejs/cli, regenerate from its local plugin module, and enforce plate generate --check <entry> in CI.
fix
font-color-toolbar-buttonmath

Preserve the active editor value while focus moves into a control, and reopen inline equations only after selection returns.

behavior
media-placeholderplate-to-htmluse-object-url

Give each browser object URL one source lifetime and revoke it when that source is replaced or unmounted.

behavior
tableuse-chat

Render the selected-cell drag handle at its cell owner and keep fake-stream cancellation inside its transport.

behavior
ai-menucolumndndeditoremoji-pickerinline-combobox

Use named React Compiler components without manual memo wrappers or display-name assignments.

remove
editorblock-menudndtableai-menucursor-overlay

The copied editor renders a Pointer Events marquee while exact one-or-many membership stays in editor selection. No selection plugin, area abstraction, or selection package is required.

fix
toolbarmedia-preview-dialogmedia-placeholder

Use native, keyboard-accessible controls for split toolbars, media upload, and image preview actions.

behavior
media-embedmedia-embed-staticpotion-iframe-demopro-iframe-demo

Restrict embedded content with explicit sandbox and referrer policies.

rename
datedate-staticmentionmention-staticfootnotefootnote-staticblock-discussion

Store dates under one `value` and persisted associations under `ref`.

rename
code-drawingcode-drawing-staticmathmath-static

Use flat lowercase drawing fields and store equation source under `latex`.

rename
columnmediatablelisteditor-pluginseditor-ai

Apply the v55 migration, field ownership, and numeric-domain contracts.

wiring
uploadthingmedia-placeholder

Keep UploadThing server routing in `uploadthing` and colocate client upload state in `media-placeholder`.

  • Keep custom upload state in your media placeholder component; `uploadthing` installs only the server router.
wiring
block-discussionemoji-picker

Keep single-consumer subscription and picker state inside their owning component families.

wiring
editor-pluginseditor-defaultdocument-migration-demo

Keep reusable editor composition migration-free and teach named lineage only in the dedicated migration example.

  • Define your named schema and migration chain in the application persistence owner; `editor-plugins` exports only `EditorKit`.
wiring
emojiemoji-pickeremoji-toolbar-buttoncalloutinline-comboboxfloating-toolbarlinktabletable-toolbar-buttonblock-menumentionmediamedia-videoblock-placeholderresize-handleblock-selectioncopilotplate-to-html

Install self-contained families with product copy, limits, styling, endpoints, and interaction policy in copied source; `table` composes renderer layout and pointer behavior directly.

behavior
basic-blocksbasic-blocks-staticheadingheading-staticlistlist-staticblock-listblock-list-statictabletable-staticmedia-videomedia-placeholdercode-blockeditor-plugins

Use one semantic Heading, boundary-only list numbering, semantic Table fields, intrinsic Media dimensions, and the generated v54 editor contract.

rename
align-staticbasic-blocks-staticbasic-marks-staticcallout-staticcode-block-staticcode-drawing-staticcolumn-staticcomment-staticdate-staticfootnote-staticexcalidrawfont-staticindent-staticline-height-staticlink-staticlist-staticmath-staticmedia-staticmention-staticsuggestion-statictable-statictoc-statictoggle-staticeditor-plugins-staticaialignautoformat-classicautoformatbasic-blocksbasic-marksbasic-nodesblock-menublock-placeholderblock-selectioncalloutcode-blockcode-drawingcolumncommentcopilotcursor-overlaydatediscussiondnddocxdocx-exporteditor-pluginsemojiexit-breakfixed-toolbar-classicfixed-toolbarfloating-toolbarfloating-toolbar-classicfootnotefontindentline-heightlinklist-classiclistmarkdownmathmediamedia-uploadthingmentionslashsuggestiontabbabletabletoctoggleeditoreditor-staticai-menuai-toolbar-buttonalign-toolbar-buttonimport-toolbar-buttonexport-toolbar-buttoncaptioncaption-staticfont-color-toolbar-buttoncomment-toolbar-buttonblock-discussionselect-editoremoji-toolbar-buttonfixed-toolbar-classic-buttonsfloating-toolbar-classic-buttonshistory-toolbar-buttonlist-toolbar-buttonindent-toolbar-buttoninline-comboboxinsert-toolbar-buttoninsert-toolbar-classic-buttonline-height-toolbar-buttonlink-toolbar-buttonlist-classic-toolbar-buttonmark-toolbar-buttonmedia-toolbarmedia-toolbar-buttonmode-toolbar-buttonmore-toolbar-buttonresize-handletable-toolbar-buttontoggle-toolbar-buttonturn-into-toolbar-buttonturn-into-toolbar-classic-buttonremote-cursor-overlaytoolbarsuggestion-toolbar-buttonblock-listblock-list-staticblockquoteblockquote-staticcodecode-staticequation-toolbar-buttonfont-size-toolbar-buttonheadingheading-statichighlighthighlight-statichorizontal-rulehorizontal-rule-staticmedia-imagemedia-image-staticmedia-preview-dialogkbdkbd-staticmedia-audiomedia-audio-staticmedia-embedmedia-embed-staticmedia-filemedia-file-staticmedia-placeholdermedia-videomedia-video-staticparagraphparagraph-staticsearch-highlighttag

Install copied editor features from one flat editor namespace

  • Move copied Plate imports from @/components/ui and @/components/editor/plugins to @/components/editor.
  • Install feature items without -kit or -base-kit suffixes; FooKit remains the exported plugin tuple.
  • Import feature-owned renderers and family-only components from the owning feature file, such as @/components/editor/footnote and @plate/footnote.
  • Use semantic renderer item names such as blockquote and media-image instead of *-node; install static renderers from their matching *-static items.
behavior
ai-menublock-draggableblock-listcallout-nodecaptioncode-drawing-nodecolumn-nodecommentemoji-nodeemoji-toolbar-buttonequation-nodeexcalidraw-nodeexport-toolbar-buttonfloating-toolbarfont-color-toolbar-buttonimport-toolbar-buttonindent-toolbar-buttoninline-comboboxlink-toolbar-buttonlink-toolbarlist-classic-nodelist-classic-toolbar-buttonlist-toolbar-buttonmedia-audio-nodemedia-embed-nodemedia-file-nodemedia-image-nodemedia-placeholder-nodemedia-preview-dialogmedia-toolbarmedia-upload-toastmedia-video-noderemote-cursor-overlayresize-handleselect-editortable-nodetable-toolbar-buttontoc-nodetoggle-nodetoggle-toolbar-buttontoolbar

Keep interaction state and behavior inside direct React 19 component families.

  • Rename the FloatingToolbar state prop to options.
wiring
emoji-kitlink-kitlist-classic-kitmedia-kit

Install the exact app-local descriptors owned by their copied UI families.

wiring
editor-kiteditor-aicopilot-demomarkdown-streaming-demoplayground-demo

Reuse the primary editor composition directly, compose Copilot only in its demo, and install `ElementIdPlugin` only when persisted IDs are required.

  • Import the primary editor composition directly outside the Copilot demo. Append CopilotKit only in copilot-demo.
  • Install ElementIdPlugin explicitly when persisted element IDs or Markdown withBlockId are required.
  • Install CodeDrawingPlugin or ExcalidrawPlugin explicitly in editors that use those specialized nodes.
  • Remove schema: EditorSchema from copied editor setup. Add an application-owned schema id and version only for persisted schema lineage, migrations, history serialization, or collaboration.
behavior
editor-kitcode-drawing-demoexcalidraw-demoplayground-demo

Keep code drawing and Excalidraw explicit in the demos that use them.

behavior
editor-kit

Derive schema identity from the installed plugin composition instead of publishing application lineage from copied registry code.

fix
ai-apiai-kitai-menuai-nodeautoformat-classic-kitautoformat-kitblock-context-menublock-discussionblock-draggableblock-selectionblock-selection-kitblock-suggestionblockquote-nodecallout-nodecode-block-nodecode-drawing-nodecode-nodecolumn-nodecomment-nodecomment-toolbar-buttoncopilot-democopilot-kitcursor-overlaydate-nodedocx-export-kiteditor-aieditor-base-kiteditor-basiceditor-kitemoji-nodeemoji-toolbar-buttonequation-nodeexcalidraw-kitexcalidraw-nodeexport-toolbar-buttonfixed-toolbar-classic-buttonsfloating-toolbarfont-base-kitfont-color-toolbar-buttonfont-kitheading-nodehighlight-nodehr-nodehuge-document-demohundreds-editors-demoimport-toolbar-buttoninline-comboboxinstallation-next-demokbd-nodelink-toolbarlist-toolbar-buttonmarkdown-streaming-demomarkdown-to-plite-demomedia-audio-nodemedia-base-kitmedia-embed-nodemedia-file-nodemedia-image-nodemedia-toolbarmedia-video-nodemode-toolbar-buttonmore-toolbar-buttonmultiple-editors-demoplate-to-htmlpreview-markdown-demoresize-handlesearch-highlight-nodeselect-editorselect-editor-demosettings-dialogslash-nodesuggestion-nodetabbable-demotable-nodetag-nodetoc-nodetransformstransforms-classicturn-into-toolbar-buttonturn-into-toolbar-classic-buttonuse-chatversion-history-demo

Install every required package and copied registry file without redundant declarations.

behavior
docx-export-kitheading-node-statictoc-nodetoc-node-static

Build live TOC navigation and DOCX-local bookmarks from runtime node keys.

wiring
editor-kiteditor-ai

Install persisted element IDs explicitly as editor product policy.

rename
ai-nodebasic-marks-base-kitblockquote-nodecallout-nodecode-block-nodecode-nodeeditable-voids-demoemoji-nodefootnote-nodeheading-nodehighlight-nodehr-nodekbd-nodelink-nodelink-node-staticlist-classic-nodeparagraph-nodesearch-highlight-nodesuggestion-nodetabbable-demotoc-nodetoggle-nodeversion-history-demo

Infer copied renderer props from their plugin descriptors.

wiring
callout-nodecallout-node-staticcode-block-nodecode-block-node-staticcode-drawing-nodecode-drawing-node-staticcolumn-nodecolumn-node-staticdate-nodedate-node-staticequation-nodeequation-node-staticexcalidraw-nodefootnote-nodefootnote-node-staticlink-nodelink-node-staticmedia-audio-nodemedia-audio-node-staticmedia-embed-nodemedia-embed-node-staticmedia-file-nodemedia-file-node-staticmedia-image-nodemedia-image-node-staticmedia-placeholder-nodemedia-toolbarmedia-video-nodemedia-video-node-staticmention-nodemention-node-staticslash-nodetable-nodetable-node-statictag-node

Infer node component props from plugin descriptors

remove
table-nodetable-base-kittable-kit

Use one table cell plugin and component for data and header cells.

  • Use TableCellElement for data and header cells. Header nodes use type: 'tableCell' with header: true.
fix
code-drawing-demoexcalidraw-demo

Configure feature renderers explicitly while installing each plugin name once.

fix
cursor-overlayghost-text

Read cursor geometry and schema-configured node ids from their package owners.

fix
media-image-nodemedia-video-node

Let resize handles derive read-only state from the editor.

fix
table-node

Use the DnD block selection-and-focus controller after row drops.

wiring
ai-kitblock-placeholder-kitblock-selection-kitcode-block-base-kitcode-block-kitcomment-kitcopilot-kitdiscussion-kitdnd-kitemoji-kitindent-base-kitindent-kitmarkdown-kitmedia-kitmention-kitslash-kitsuggestion-kittabbable-kittoc-kit

Seed editor-local plugin stores with `initialState`.

behavior
ai-menuai-nodeblock-context-menublock-discussionblock-draggableblock-selectionblock-suggestioncolumn-nodecomment-nodecursor-overlayemoji-nodefloating-toolbarghost-textlink-toolbarmedia-placeholder-nodemedia-upload-toastmode-toolbar-buttonsettings-dialogsuggestion-nodesuggestion-toolbar-buttontable-nodeuse-chat

Read, update, and subscribe to plugin state through the typed store API.

behavior
collaboration-demomarkdown-streaming-demo

Configure and update plugin store state through the same API used by copied registry code.

behavior
basic-marks-kitbasic-marks-base-kitmark-toolbar-buttonmore-toolbar-buttonbasic-marks-valueplugin-rules-value

Use one script mark for subscript and superscript

  • For persisted v53 script marks, add migratePlateV54 to the app's defineDocumentMigrations chain.
wiring
basic-blocks-base-kitbasic-marks-base-kitcallout-base-kitcode-block-base-kitcode-drawing-base-kitcolumn-base-kitcomment-base-kitdate-base-kitfootnote-base-kitlink-base-kitmath-base-kitmedia-base-kitmention-base-kitsuggestion-base-kittable-base-kittoc-base-kittoggle-base-kit

Bind static components without React plugin imports

wiring
markdown-kit

Configure Markdown parsing without installing a renderer-specific Footnote preset.

wiring
editor-kiteditor-aieditor-base-kit

Compose the shared Markdown parser with the matching live or static Footnote renderers exactly once.

wiring
align-kitbasic-blocks-kitbasic-marks-kitcallout-kitcode-block-kitcode-drawing-kitcolumn-kitcomment-kitdate-kiteditor-aieditor-base-kiteditor-kitfont-kitline-height-kitlink-kitlist-kitmarkdown-kitmath-kitmedia-kitmention-kitsuggestion-kittable-kittoc-kittoggle-kit

Install only the presets their copied source composes, without unrelated static/base counterparts.

behavior
tabbable-kit

Preserve Indent support while reserving Tab and Shift+Tab navigation for tabbable elements.

behavior
font-color-toolbar-buttontoolbarfixed-toolbar-buttonstable-node

Configure text color palettes, optionally control recent colors, and navigate color grids with the keyboard.

wiring
mark-toolbar-buttonfont-color-toolbar-buttonfont-size-toolbar-buttonmedia-toolbar-buttonmore-toolbar-buttonfixed-toolbar-buttonsfloating-toolbar-buttonsinstallation-next-02-marks-demoinstallation-next-03-elements-demoinstallation-next-demo

Use installed feature plugins instead of raw type dispatch in toolbar controls.

behavior
captionmedia-base-kitmedia-kitmedia-image-nodemedia-video-nodemedia-audio-nodemedia-file-nodemedia-embed-nodemedia-placeholder-nodemedia-toolbar-buttonplate-to-html

Render direct media caption children, keep an empty caption hidden until asset focus, and separate asset selection from caption editing.

  • Remove CaptionPlugin from media kits and render each media element's direct children as its caption; use [{ text: '' }] when the caption is absent.
  • For persisted v53 media captions, add migratePlateV54 to the app's defineDocumentMigrations chain.
wiring
footnote-nodetable-nodetable-node-static

Use the installed plugin's typed editor APIs with Plate v54.

behavior
transformstransforms-classiceditor-kitdnd-kit

Insert code blocks, drawings, Excalidraw canvases, tables, and dropped media through their installed commands with explicit relational or path placement.

v53.2.2

@platejs/ai

Bug Fixes

  • Preserve streaming trailing whitespace with markdown hard breaks. (#5026)

@platejs/markdown

Bug Fixes

  • Serialize hard line breaks inside paragraph text nodes. (#5026)

v53.2.1...v53.2.2 · By @zbeyens

v53.2.1

@platejs/core

Bug Fixes

  • Add node.isMetadataProp and editor.api.isElementStateEmpty for element state checks. (#5029)

platejs

Bug Fixes

  • Updated @platejs/core, @platejs/utils.

@platejs/utils

Bug Fixes

  • Fix block placeholders on single empty list items. (#5029)

v53.2.0...v53.2.1 · By @zbeyens

v53.1.6

@platejs/ai

Bug Fixes

  • Updated @platejs/selection.

@platejs/selection

Bug Fixes

  • Fix block selection copy and cut in Safari by writing selected blocks to the native clipboard event. (#5018)

v53.1.5...v53.1.6 · By @zbeyens

v53.1.5

@platejs/ai

Bug Fixes

  • Updated @platejs/markdown.

@platejs/markdown

Bug Fixes

  • Fix markdown deserialization from crashing on malformed HTML-like MDX input. (#5016)

v53.1.4...v53.1.5 · By @zbeyens

v53.1.2

@platejs/core

Bug Fixes

  • Fix complex leaf and text renderers triggering React hook-order warnings when marks change. (#5009)

platejs

Bug Fixes

  • Updated @platejs/core, @platejs/utils.

@platejs/utils

Bug Fixes

  • Updated @platejs/core.

v53.1.1...v53.1.2 · By @zbeyens

v53.1.1

@platejs/ai

Bug Fixes

  • Updated @platejs/markdown.

@platejs/markdown

Bug Fixes

  • Fix MDX fallback deserialization for GFM tables containing plain less-than text. (#5007)

v53.1.0...v53.1.1 · By @kiranmagic7

v53.1.0

@platejs/docx-io

Features

  • Forward two dropped options in exportToDocx: (#4997)

    • pageSize — the html-to-docx engine accepts a page size, but exportToDocx only forwarded margins and orientation, so the document was always the default (US Letter). You can now pass e.g. pageSize: { width: 11906, height: 16838 } to export A4.
    • fontFamily — it was only applied to the serialized HTML (and only when an EditorStaticComponent was provided), so the document default font was never set and Word fell back to Times New Roman. It now also sets the document default font (documentOptions.font).

@platejs/dnd

Bug Fixes

  • Clear stale DnD drop indicators when dragging from a block into editor whitespace. (#5002)

v53.0.9...v53.1.0 · By @WilliamPeralta, @kiranmagic7

v53.0.8

@platejs/docx-io

Bug Fixes

  • Fix exportToDocx adding blank paragraphs at the top of the document. wrapHtmlForDocx emitted a <!DOCTYPE html> and indented the template; html-to-docx (html-to-vdom) keeps the DOCTYPE and the whitespace-only text nodes between tags and renders each as a blank paragraph. The wrapper now emits tight markup with no DOCTYPE. (#4991)

v53.0.7...v53.0.8 · By @WilliamPeralta

v53.0.7

@platejs/ai

Bug Fixes

  • Updated @platejs/table.

@platejs/core

Bug Fixes

  • Add transformInitialValue and nodeId.initialValueIds while keeping normalizeInitialValue as a deprecated alias (#4987)

  • Improve large-document mount and render performance across core element, mark, and nodeId paths (#4987)

    • Cut 10k mixed-document core mount time from 1240.60 ms to 468.26 ms without nodeId (62.3%, 2.65x faster)
    • Cut 10k mixed-document core mount time from 1290.66 ms to 477.73 ms with nodeId (63.0%, 2.70x faster)
    • Cut mixed-document nodeId overhead over core from +50.06 ms to +9.46 ms (81.1% smaller)
    • Cut duplicate-id paste cost from 20.06 ms to 13.79 ms (31.2%, 1.45x faster)
    • Cut 10k code-only mount time from 1500.30 ms to 496.47 ms (66.9%, 3.02x faster) and shrink the code-only tax over core from +280.75 ms to +27.89 ms (90.1% smaller)
    • Bring the current 10k core and basic large-document mount lanes to Plite parity or better (core -3.5%, core + nodeId -1.6%, basic -1.2%)
    • Preserve Plite children for void render.as tags and Plite attributes on simple leaf/text render paths

platejs

Bug Fixes

  • Updated @platejs/core, @platejs/plite, @platejs/utils.

@platejs/plite

Bug Fixes

  • Add experimental editor.tf.setNodesBatch for exact-path node prop updates on large documents (#4987)

    • Cut the large-document set_node hot path from 18.56 ms to 2.63 ms at 1k blocks (7.05x faster)
    • Cut the same path from 118.54 ms to 4.92 ms at 5k blocks (24.10x faster)
    • Let @platejs/core batch live nodeId normalization instead of paying one setNodes call per missing id
    • Keep editor.tf.setNodesBatch explicitly temporary. It is experimental and will be removed in a future release
  • Updated plite-hyperscript. (231b986)

@platejs/table

Bug Fixes

  • Speed up unmerged table range selection while preserving merged-cell handling (#4987)

@platejs/utils

Bug Fixes

  • Updated @platejs/core, @platejs/plite.

v53.0.6...v53.0.7 · By @zbeyens, @github-actions[bot]

v53.0.6

@platejs/core

Bug Fixes

  • Fix .configure({ inputRules }) losing rules on subsequent editor instances (#4983)

    The user's config object was shared across resolutions via closure; clearing inputRules on the first resolve left later editors (StrictMode remounts, HMR, multi-editor pages) with no configured rules.

  • Fix createTextSubstitutionInputRule not firing on the final character of flat matches (e.g. ->, (c)©) (#4983)

platejs

Bug Fixes

  • Updated @platejs/core, @platejs/utils.

@platejs/utils

Bug Fixes

  • Updated @platejs/core.

v53.0.5...v53.0.6 · By @bbyiringiro

v53.0.5

platejs

Bug Fixes

  • Updated @platejs/core, @platejs/plite, @platejs/utils.

@platejs/plite

Bug Fixes

@platejs/utils

Bug Fixes

  • Updated @platejs/core, @platejs/plite.

v53.0.4...v53.0.5 · By @github-actions[bot]

v53.0.3

@platejs/ai

Bug Fixes

  • Clear block streaming state when aiChat.stop() stops generation (#4945)

@platejs/link

Bug Fixes

  • Fix empty link normalization when suggestion acceptance removes the last link character (#4945)

platejs

Bug Fixes

  • Updated @platejs/utils.

@platejs/suggestion

Bug Fixes

  • Fix inline-void delete and replace suggestions around mentions and paragraph boundaries (#4945)

@platejs/utils

Bug Fixes

  • Add a trailing-block insert hook for normalization-driven insert behavior (#4945)

CHANGELOG · v53.0.2...v53.0.3 · By @felixfeng33

behavior
block-discussiondiscussion-kitcomment-kit

Rebuild discussion indexes from editor state so accept and reject updates, inline text summaries, and deleted comments stay current.

fix
suggestionsuggestion-base-kitsuggestion-kitsuggestion-node

Fix block and inline suggestion regressions around inline voids, links, line breaks, block voids, and trailing-block normalization.

behavior
block-listcode-drawing-nodedate-nodeequation-nodeexcalidraw-nodemedia-audio-nodemedia-embed-nodemedia-file-nodemedia-image-nodemedia-video-nodemention-nodelink-node

Keep Plite children mount points and suggestion wrappers stable for static and media-style nodes.

v53.0.0

@platejs/autoformat

Breaking Changes

  • Deprecate @platejs/autoformat. Markdown shortcuts and text substitutions are now authored as inputRules on each feature plugin, and AutoformatPlugin remains only as an inert compatibility export. (#4941)

    Migration:

    1. Remove AutoformatPlugin from your plugins and replace @platejs/autoformat after migrating rules.
    2. Replace each old AutoformatRule with the matching rule factory on the plugin that owns the feature. See the table below.
    3. Replace symbol substitutions (arrows, fractions, smart quotes, legal, math operators) with createTextSubstitutionInputRule registered on a local createBasePlugin.
    4. Replace rules[].query with enabled on the rule factory call. Replace the global code-block guard with a per-plugin enabled check.
    5. Drop enableUndoOnDelete — undo-on-delete is the built-in behavior.
    6. Replace custom AutoformatRule definitions with createRuleFactory from platejs.
    // Before
    import { AutoformatPlugin } from "@platejs/autoformat";
     
    const editor = createPlateEditor({
      plugins: [
        AutoformatPlugin.configure({
          options: {
            enableUndoOnDelete: true,
            rules: [
              { match: "# ", mode: "block", type: KEYS.h1 },
              { match: "**", mode: "mark", type: KEYS.bold },
              {
                match: "* ",
                mode: "block",
                type: "list",
                format: (editor) =>
                  toggleList(editor, { listStyleType: KEYS.ul }),
              },
            ],
          },
        }),
      ],
    });
     
    // After
    import { BoldRules } from "@platejs/basic-nodes";
    import { BoldPlugin } from "@platejs/basic-nodes/react";
    import { HeadingRules } from "@platejs/basic-nodes";
    import { H1Plugin } from "@platejs/basic-nodes/react";
    import { BulletedListRules } from "@platejs/list";
    import { ListPlugin } from "@platejs/list/react";
     
    const editor = createPlateEditor({
      plugins: [
        H1Plugin.configure({ inputRules: [HeadingRules.markdown()] }),
        BoldPlugin.configure({
          inputRules: [BoldRules.markdown({ variant: "*" })],
        }),
        ListPlugin.configure({
          inputRules: [BulletedListRules.markdown({ variant: "-" })],
        }),
      ],
    });

    Rule Map

    Basic blocks — @platejs/basic-nodes

    Old ruleNew rule
    { match: '# '..'###### ', mode: 'block', type: KEYS.h1..h6 }HxPlugin.configure({ inputRules: [HeadingRules.markdown()] }) — register on each H1Plugin..H6Plugin
    { match: '> ', mode: 'block', type: KEYS.blockquote }BlockquotePlugin.configure({ inputRules: [BlockquoteRules.markdown()] })
    { match: ['---', '—-', '___ '], mode: 'block', type: KEYS.hr }HorizontalRulePlugin.configure({ inputRules: [HorizontalRuleRules.markdown({ variant: '-' }), HorizontalRuleRules.markdown({ variant: '_' })] })

    Basic marks — @platejs/basic-nodes

    Old ruleNew ruleOwning plugin
    { match: '**', mode: 'mark', type: KEYS.bold }BoldRules.markdown({ variant: '*' })BoldPlugin
    { match: '__', mode: 'mark', type: KEYS.underline }UnderlineRules.markdown()UnderlinePlugin
    { match: '*', mode: 'mark', type: KEYS.italic }ItalicRules.markdown({ variant: '*' })ItalicPlugin
    { match: '_', mode: 'mark', type: KEYS.italic }ItalicRules.markdown({ variant: '_' })ItalicPlugin
    { match: '`', mode: 'mark', type: KEYS.code }CodeRules.markdown()CodePlugin
    { match: '~~', mode: 'mark', type: KEYS.strikethrough }StrikethroughRules.markdown()StrikethroughPlugin
    { match: '~', mode: 'mark', type: KEYS.sub }SubscriptRules.markdown()SubscriptPlugin
    { match: '^', mode: 'mark', type: KEYS.sup }SuperscriptRules.markdown()SuperscriptPlugin
    { match: '==', mode: 'mark', type: KEYS.highlight }HighlightRules.markdown({ variant: '==' })HighlightPlugin
    { match: '≡', mode: 'mark', type: KEYS.highlight }HighlightRules.markdown({ variant: '≡' })HighlightPlugin
    { match: '***', mode: 'mark', type: [bold, italic] }MarkComboRules.markdown({ variant: 'boldItalic' })BoldPlugin
    { match: '__*', mode: 'mark', type: [underline, italic] }MarkComboRules.markdown({ variant: 'italicUnderline' })BoldPlugin
    { match: '__**', mode: 'mark', type: [underline, bold] }MarkComboRules.markdown({ variant: 'boldUnderline' })BoldPlugin
    { match: '___***', mode: 'mark', type: [underline, bold, italic] }MarkComboRules.markdown({ variant: 'boldItalicUnderline' })BoldPlugin

    Register each family on its owning plugin:

    BoldPlugin.configure({
      inputRules: [
        BoldRules.markdown({ variant: "*" }),
        BoldRules.markdown({ variant: "_" }),
        MarkComboRules.markdown({ variant: "boldItalic" }),
        MarkComboRules.markdown({ variant: "boldUnderline" }),
        MarkComboRules.markdown({ variant: "boldItalicUnderline" }),
        MarkComboRules.markdown({ variant: "italicUnderline" }),
      ],
    });

    Code block — @platejs/code-block

    Old ruleNew rule
    { match: '```', mode: 'block', type: KEYS.codeBlock, format: insertEmptyCodeBlock }CodeBlockPlugin.configure({ inputRules: [CodeBlockRules.markdown({ on: 'match' })] })

    Lists — @platejs/list and @platejs/list-classic

    Old ruleNew rule
    { match: ['- ', '* '], mode: 'block', format: toggleList(..., { listStyleType: KEYS.ul }) }BulletedListRules.markdown({ variant: '-' }), BulletedListRules.markdown({ variant: '*' })
    { match: /^\d+\.$ |^\d+\)$ /, matchByRegex: true, format: toggleList(..., { listStyleType: KEYS.ol }) }OrderedListRules.markdown({ variant: '.' }), OrderedListRules.markdown({ variant: ')' })
    { match: '[] ', mode: 'block', format: toggleList(..., { listStyleType: KEYS.listTodo }) }TaskListRules.markdown({ checked: false })
    { match: '[x] ', mode: 'block', format: toggleList + setNodes({ checked: true }) }TaskListRules.markdown({ checked: true })
    ListPlugin.configure({
      inputRules: [
        BulletedListRules.markdown({ variant: "-" }),
        BulletedListRules.markdown({ variant: "*" }),
        OrderedListRules.markdown({ variant: "." }),
        OrderedListRules.markdown({ variant: ")" }),
        TaskListRules.markdown({ checked: false }),
        TaskListRules.markdown({ checked: true }),
      ],
    });

    Replace @platejs/list with @platejs/list-classic imports when using the classic list model. The factory names are identical.

    Math — @platejs/math

    Old ruleNew rule
    Inline equation $…$InlineEquationPlugin.configure({ inputRules: [MathRules.markdown({ variant: '$' })] })
    Block equation $$…$$EquationPlugin.configure({ inputRules: [MathRules.markdown({ on: 'break', variant: '$$' })] })

    Link — @platejs/link

    Old behaviorNew rule
    [text](url) markdownLinkRules.markdown()
    Autolink on pasteLinkRules.autolink({ variant: 'paste' })
    Autolink on spaceLinkRules.autolink({ variant: 'space' })
    Autolink on EnterLinkRules.autolink({ variant: 'break' })
    LinkPlugin.configure({
      inputRules: [
        LinkRules.markdown(),
        LinkRules.autolink({ variant: "paste" }),
        LinkRules.autolink({ variant: "space" }),
        LinkRules.autolink({ variant: "break" }),
      ],
    });

    Text substitutions (arrows, fractions, legal, math operators, smart quotes)

    Move these to a local createBasePlugin with createTextSubstitutionInputRule:

    import {
      createBasePlugin,
      createTextSubstitutionInputRule,
      KEYS,
    } from "platejs";
     
    const isTextSubstitutionBlocked = (editor) =>
      editor.api.some({ match: { type: [editor.getType(KEYS.codeBlock)] } });
     
    const ShortcutsPlugin = createBasePlugin({
      key: "shortcuts",
      inputRules: [
        createTextSubstitutionInputRule({
          enabled: ({ editor }) => !isTextSubstitutionBlocked(editor),
          patterns: [
            { format: "→", match: "->" },
            { format: "⇒", match: "=>" },
            { format: "½", match: "1/2" },
            { format: "™", match: ["(tm)", "(TM)"] },
            { format: ["“", "”"], match: '"' },
          ],
        }),
      ],
    });

    Each pattern set is just data — autoformatArrow, autoformatLegal, autoformatMath, autoformatPunctuation, autoformatSmartQuotes, and autoformatLegalHtml from the old package map 1:1 onto patterns arrays. AutoformatKit in the Plate registry is pre-built with all of them.

    Custom rules

    Old AutoformatRule objects have no direct replacement. Build a rule family with createRuleFactory:

    import { createRuleFactory } from "platejs";
     
    const MyRules = {
      markdown: createRuleFactory({
        type: "blockMatch",
        match: "!! ",
        format: "my-block",
      }),
    };
     
    MyPlugin.configure({ inputRules: [MyRules.markdown()] });

    Option removals

    • enableUndoOnDelete — removed. Backspace on a rule-inserted node restores the source text by default.
    • rules[].query — replaced by enabled on the rule factory call.
    • rules[].preFormat / rules[].format — replaced by rule-family format and resolve callbacks inside createRuleFactory.
    • rules[].trigger — rule families set their own trigger. Override it with the trigger option on a custom createRuleFactory call.

    See the Autoformat doc for the kit path and the Plugin Input Rules guide for the full runtime.

@platejs/basic-nodes

Breaking Changes

  • Store blockquotes as container blocks with block children. Lift every selected nested quoted block one level on Shift+Tab. Reset headings to paragraphs on Backspace at block start before any merge. (#4941)

    Migration:

    1. Update persisted values, fixtures, and tests to use block children instead of direct text children.
    2. Expect editor.tf.blockquote.toggle() to wrap or unwrap blocks instead of retagging one text block in place.
    3. Empty later quoted paragraphs delete in place on Backspace instead of jumping out of the quote.
    4. Backspace at the start of a heading now resets the heading to a paragraph before any merge.
    5. Legacy flat blockquote values still normalize on load, but persisted snapshots and fixtures should move to the new shape.
    // Before
    { type: 'blockquote', children: [{ text: 'Quote' }] }
     
    // After
    {
      type: 'blockquote',
      children: [{ type: 'p', children: [{ text: 'Quote' }] }],
    }

@platejs/code-block

Breaking Changes

  • Keep Backspace at the start of a non-empty first code line inside the code block. Merge an empty inner code line into the previous code line instead of unwrapping the block. (#4941)

@platejs/markdown

Breaking Changes

  • Round-trip blockquotes as nested block content instead of flat newline-packed text. Serialize image titles from node.title instead of copying the caption into the markdown title slot. Preserve MDX media attribute expressions during markdown serialization instead of stringifying them into JSON text. Serialize plain URL links back to bare URL markdown instead of bracket-link form. Round-trip footnote references and definitions as dedicated footnote nodes instead of collapsing them to plain-text fallback. (#4941)

    Migration:

    1. Update snapshots and direct value assertions to expect blockquote.children to contain block nodes such as paragraphs and lists.
    2. If you generate initial editor values from markdown, hydrate blockquotes with paragraph children instead of flat text.
    3. If you want markdown output like ![alt](url "title"), set node.title. Images without a title now serialize as ![alt](url).
    4. If you serialize MDX media nodes with expression attributes like width={640}, expect those expressions to stay as expressions instead of turning into quoted JSON.
    5. Plain URL links such as https://platejs.org now serialize as bare URLs instead of [https://platejs.org](https://platejs.org).
    6. If you enable footnote-aware markdown input, install @platejs/footnote and include BaseFootnoteReferencePlugin and BaseFootnoteDefinitionPlugin so footnote nodes have real editor semantics instead of falling back to unknown node types.
    // Before
    { type: 'blockquote', children: [{ text: 'Quote\\nNext line' }] }
     
    // After
    {
      type: 'blockquote',
      children: [
        { type: 'p', children: [{ text: 'Quote' }] },
        { type: 'p', children: [{ text: 'Next line' }] },
      ],
    }

Bug Fixes

  • Write canonical date nodes as <date value="..."/> and round-trip normalized media embed metadata (#4941)

  • Preserve unknown MDX and raw HTML block source more faithfully during markdown deserialization fallback (#4941)

@platejs/table

Breaking Changes

  • Escalate the second selectAll from the current table to the whole document. (#4941)

@platejs/core

Features

  • Add lift as a break and delete rule action for blocks that should leave one ancestor level instead of resetting or exiting. Reset the trailing block to a paragraph when splitReset handles selected heading text. (#4941)

Bug Fixes

  • Add createRuleFactory for building input rule families with overridable defaults and required options (#4941)

  • Add useNavigationHighlight(path) for React node components that need the current navigation-feedback target without reading plugin options directly (#4941)

@platejs/footnote

Features

  • Add FootnoteReferencePlugin, FootnoteDefinitionPlugin, and FootnoteInputPlugin for real footnote nodes and inline [^ combobox insertion in Plate editors. (#4941)

@platejs/date

Bug Fixes

  • Store date nodes as canonical YYYY-MM-DD values and preserve unparseable legacy text as fallback data (#4941)

@platejs/link

Bug Fixes

  • Keep pasted URLs literal inside markdown link source entry by default (#4941)

@platejs/list

Bug Fixes

  • Allow list markdown rule families to override shared runtime rule fields while keeping semantic variant and checked options (#4941)

@platejs/list-classic

Bug Fixes

  • Allow classic list markdown rule families to override shared runtime rule fields while keeping semantic variant and checked options (#4941)

@platejs/media

Bug Fixes

  • Support allowlisted Twitter/X embed snippet extraction in media embed URL transforms (#4941)

  • Normalize supported media embeds into canonical provider metadata and preserve source URLs for embed editing (#4941)

@platejs/plite

Bug Fixes

  • Updated slate, plite-dom. (081cbe9)

@platejs/toc

Bug Fixes

  • Add active section state to useTocElementState so TOC elements can mark the current heading while the document scrolls (#4941)

  • Fix TOC activation to navigate without entering block-selection mode (#4941)

@platejs/utils

Bug Fixes

  • Add KEYS.footnoteDefinition, KEYS.footnoteReference, and KEYS.footnoteInput (#4941)

CHANGELOG · v52.3.22...v53.0.0 · By @zbeyens, @github-actions[bot]

new
footnote-kit

Add GFM footnotes with reference, definition, and input plugins plus UI — hover preview, navigation flash, multi-reference picker, and duplicate-definition recovery.

new
footnote-base-kit

Add static footnote rendering for SSR and markdown pipelines.

new
editor-kiteditor-base-kit

Register the new footnote kits.

wiring
markdown-kit

Register the base footnote plugins and add `remark-emoji` so GFM footnotes and emoji shortcodes round-trip.

Add a Footnote insert entry wired to `action_footnote`.

Map `action_footnote` to `insertFootnote(editor, { select: true })`.

behavior
autoformat-kit

Reduce to symbol substitutions (arrows, fractions, legal, punctuation, smart quotes, sub/superscript). Markdown shortcuts move to the feature kits below. Add link automd so `[text](url)` closing on `)` resolves to a structured link.

Older releases