Releases

RSS

Latest updates and announcements.

v54.0.0-beta.0

@platejs/core

Breaking Changes

  • Prepare v54 beta prerelease versioning. (#5031)

@platejs/slate

Breaking Changes

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

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

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 Slate parity or better (core -3.5%, core + nodeId -1.6%, basic -1.2%)
    • Preserve Slate children for void render.as tags and Slate attributes on simple leaf/text render paths

platejs

Bug Fixes

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

@platejs/slate

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 slate-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/slate.

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/slate, @platejs/utils.

@platejs/slate

Bug Fixes

@platejs/utils

Bug Fixes

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

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

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

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

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 createSlatePlugin.
    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 createSlatePlugin with createTextSubstitutionInputRule:

    import {
      createSlatePlugin,
      createTextSubstitutionInputRule,
      KEYS,
    } from "platejs";
     
    const isTextSubstitutionBlocked = (editor) =>
      editor.api.some({ match: { type: [editor.getType(KEYS.codeBlock)] } });
     
    const ShortcutsPlugin = createSlatePlugin({
      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/slate

Bug Fixes

  • Updated slate, slate-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]

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

Add static footnote rendering for SSR and markdown pipelines.

Register the new footnote kits.

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 })`.

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