From zbeyens. The source code is available on GitHub.

Plate
PlatePliteEditorsTemplates
GitHub16kGitHub
DiscordDiscord
  • Feature Kits
  • Plugin
    • Plugin Methods
    • Plugin Shortcuts
    • Plugin Context
    • Plugin Components
    • Plugin Rules
    • Editing Behavior
    • Plugin Input Rules
  • Editor
    • Editor Methods
    • Controlled Value
  • Performance
  • Static Rendering
  • HTML
  • Markdown
  • Form
  • TypeScript
  • Debugging
  • Unit Testing
  • Browser
  • Troubleshooting

Plugin Input Rules

PreviousNext

Typed editor rules for markdown shortcuts, block fences, autolinks, and text substitutions.

Plugin Input Rules convert typed editor patterns: markdown prefixes become headings, fences become code blocks, URLs become links, and -> becomes →. Use Plugin Rules for node behavior policy such as how Enter or Backspace works inside a block. This page covers shipped markdown rules, local substitutions, custom authoring, execution order, and helper reference.

On This Page

  • What Plugin Input Rules Are
  • Quick Start
  • Feature-Owned Markdown Rules
  • Local Copied Shortcuts
  • Custom Rules
Editing BehaviorEditor

On This Page

On This PageWhat Plugin Input Rules AreQuick StartAdd Feature-Owned Markdown RulesAdd Local Text SubstitutionsFeature-Owned Markdown RulesBasic BlocksBasic MarksCode BlocksListsMathLinksLocal Copied ShortcutscreateTextSubstitutionInputRuleUse AutoformatKit As A Starting PointWhen To Reach For defineInputRuleCustom RulesRegister Explicit Rule InstancesPlugin-Side FactoriesAuthor A Rule FamilyHow Rule Execution WorksTargetsSelection ContextLifecycleAPI ReferenceRule TargetsdefineInputRulecreateRuleFactorycreateMarkInputRulecreateBlockStartInputRulecreateBlockFenceInputRulecreateTextSubstitutionInputRulematchDelimitedInlinematchBlockStartPackage Rule Families
Build your editor
Production-ready AI template and reusable components.
Get all-access
How Rule Execution Works
  • API Reference
  • What Plugin Input Rules Are

    When you type a character, press Enter, or paste data, Plate asks every registered input rule "does this fire?" before running the default transform. Each rule declares a target (insertText, insertBreak, or insertData), an optional enabled gate, a resolve function that looks at the current selection and returns a match payload, and an apply function that performs the transform. The first rule whose resolve returns a non-undefined payload gets to run; if nothing matches, the default transform runs as usual.

    Ownership splits cleanly into three lanes:

    • Core. Owns dispatch, selection helpers, and the low-level authoring surfaces: createMarkInputRule, createBlockStartInputRule, createBlockFenceInputRule, createTextSubstitutionInputRule, createRuleFactory, and defineInputRule.
    • Feature packages. Own semantic rule families like HeadingRules, BlockquoteRules, CodeBlockRules, BulletedListRules, MathRules, LinkRules. Each family exports factory functions that return concrete rule instances.
    • Kits and apps. Own activation. Nothing is turned on just because a plugin exists — you pass rule instances to inputRules: [...] when you configure a plugin.
    LaneOwnerExample
    Feature markdown rulePackageHeadingRules.markdown()
    Feature interaction rulePackageLinkRules.autolink({ variant: 'space' })
    Local text substitutionApp / local kitcreateTextSubstitutionInputRule({ patterns })
    Raw custom ruleApp or packagedefineInputRule({ target, trigger, resolve, apply })

    Input rules are always explicit. Registering a plugin does not activate any rules; you must pass them to inputRules. There is no hidden default set and no string-keyed activation layer.

    Quick Start

    Input rules ship as concrete instances you pass into a plugin's inputRules array. The two fastest setup paths are:

    1. Drop in feature kits that register feature-owned markdown rules — headings, marks, code blocks, lists, math, links.
    2. Drop in a local AutoformatKit to get common text substitutions like -> → → or (c) → ©.

    You can use one, both, or neither.

    Add Feature-Owned Markdown Rules

    Use the same kits you already use for nodes and marks. Each kit registers its own markdown rules on the right plugins, so you don't wire anything by hand:

    import { createPlateEditor } from 'platejs/react';
     
    import { BasicBlocksKit } from '@/components/editor/basic-blocks';
    import { BasicMarksKit } from '@/components/editor/basic-marks';
    import { CodeBlockKit } from '@/components/editor/code-block';
    import { LinkKit } from '@/components/editor/link';
    import { ListKit } from '@/components/editor/list';
    import { MathKit } from '@/components/editor/math';
     
    const editor = createPlateEditor({
      plugins: [
        ...BasicBlocksKit,
        ...BasicMarksKit,
        ...CodeBlockKit,
        ...ListKit,
        ...LinkKit,
        ...MathKit,
      ],
    });
    import { createPlateEditor } from 'platejs/react';
     
    import { BasicBlocksKit } from '@/components/editor/basic-blocks';
    import { BasicMarksKit } from '@/components/editor/basic-marks';
    import { CodeBlockKit } from '@/components/editor/code-block';
    import { LinkKit } from '@/components/editor/link';
    import { ListKit } from '@/components/editor/list';
    import { MathKit } from '@/components/editor/math';
     
    const editor = createPlateEditor({
      plugins: [
        ...BasicBlocksKit,
        ...BasicMarksKit,
        ...CodeBlockKit,
        ...ListKit,
        ...
    
    
    

    Typing # creates an H1, **bold** turns on the bold mark, a triple-backtick fence creates a code block, - starts a bulleted list, [label](url) creates a link, and so on. Each kit owns its rule wiring — the kit source shows exactly which rules it registers and on which plugins.

    Add Local Text Substitutions

    AutoformatKit is copied registry code that lives in your app and uses createTextSubstitutionInputRule under the hood. You own the code and can edit the patterns.

    import { createPlateEditor } from 'platejs/react';
     
    import { AutoformatKit } from '@/components/editor/autoformat';
     
    const editor = createPlateEditor({
      plugins: [...AutoformatKit],
    });
    import { createPlateEditor } from 'platejs/react';
     
    import { AutoformatKit } from '@/components/editor/autoformat';
     
    const editor = createPlateEditor({
      plugins: [...AutoformatKit],
    });

    Type -> and get →. Type (c) and get ©. The full pattern list is visible in the kit source — change it however you like.

    Feature-owned rules and text substitutions are different lanes. Markdown shortcuts live in the feature packages that own the semantics (@platejs/basic-nodes, @platejs/link, @platejs/math, ...). Text substitutions are glyph-for-glyph replacements that live in your app.

    Kits are the quick path. If you'd rather wire rules by hand — pick which markdown variants are active, override priorities, or gate a rule per-app — jump to Feature-Owned Markdown Rules for the manual path.

    That's the whole surface in under a minute. Type # , type ->, watch them land.

    Feature-Owned Markdown Rules

    Feature packages export semantic rule families. Each family exposes one or more factory functions that return a concrete rule you pass into the matching plugin's inputRules.

    Basic Blocks

    Basic block rules ship with @platejs/basic-nodes. Register them per plugin.

    Headings. HeadingRules.markdown() is the same factory for H1 through H6. It reads the heading depth from a resolved persisted type matching h1 through h6, so you pass it once on each heading plugin. If an application remaps a heading to a different type, provide an app-owned rule for that type instead.

    import { HeadingRules } from '@platejs/basic-nodes';
    import {
      HeadingPlugin,
      
      
      
      
      
    } from '@platejs/basic-nodes/react';
     
    export const HeadingInputRulesKit = [
      HeadingPlugin.configure({ inputRules: [HeadingRules.markdown()] }),
      HeadingPlugin.configure({ inputRules: [HeadingRules.markdown()] }),
      HeadingPlugin.configure({ inputRules: [HeadingRules.markdown()] }),
      HeadingPlugin.configure({ inputRules: [HeadingRules.markdown()] }),
      HeadingPlugin.configure({ inputRules: [HeadingRules.markdown()] }),
      HeadingPlugin.configure({ inputRules: [HeadingRules.markdown()] }),
    ] as const;
    import { HeadingRules } from '@platejs/basic-nodes';
    import {
      HeadingPlugin,
      
      
      
      
      
    } from '@platejs/basic-nodes/react';
     
    export const HeadingInputRulesKit = [
      HeadingPlugin.configure({ inputRules: [HeadingRules.markdown()] }),
      HeadingPlugin.configure({ inputRules: [HeadingRules.markdown()] }),
      HeadingPlugin.configure({ inputRules: [HeadingRules.markdown()] }),
      HeadingPlugin.configure({ inputRules: [HeadingRules.markdown()] }),
      HeadingPlugin.configure({ inputRules: [HeadingRules.markdown()] }),
      HeadingPlugin.configure
    

    Blockquote. BlockquoteRules.markdown() fires on > followed by space and wraps the current block. Because blockquote is a wrapper/container node, the rule nests cleanly inside an existing quote instead of trying to retag the paragraph in place. The rule is gated with enabled so it won't fire inside a code block.

    import { BlockquoteRules } from '@platejs/basic-nodes';
    import { BlockquotePlugin } from '@platejs/basic-nodes/react';
     
    export const BlockquoteInputRulesPlugin = BlockquotePlugin.configure({
      inputRules: [BlockquoteRules.markdown()],
    });
    import { BlockquoteRules } from '@platejs/basic-nodes';
    import { BlockquotePlugin } from '@platejs/basic-nodes/react';
     
    export const BlockquoteInputRulesPlugin = BlockquotePlugin.configure({
      inputRules: [BlockquoteRules.markdown()],
    });

    Horizontal rule. HorizontalRuleRules.markdown() takes a variant so you can register more than one trigger.

    import { HorizontalRuleRules } from '@platejs/basic-nodes';
    import { HorizontalRulePlugin } from '@platejs/basic-nodes/react';
     
    export const HorizontalRuleInputRulesPlugin = HorizontalRulePlugin.configure({
      inputRules: [
        HorizontalRuleRules.markdown({ variant: '-' }),
        HorizontalRuleRules.markdown({ variant: '_' }),
      ],
    });
    import { HorizontalRuleRules } from '@platejs/basic-nodes';
    import { HorizontalRulePlugin } from '@platejs/basic-nodes/react';
     
    export const HorizontalRuleInputRulesPlugin = HorizontalRulePlugin.configure({
      inputRules: [
        HorizontalRuleRules.markdown({ variant: '-' }),
        HorizontalRuleRules.markdown({ variant: '_' }),
      ],
    });

    --- and ___ both create a horizontal rule. Register only the variants you want to support.

    Basic Marks

    Mark rules live in the same package. Each factory returns a single rule, so register one per trigger you want to support.

    Bold, italic, underline.

    import {
      BoldRules,
      ItalicRules,
      UnderlineRules,
    } from '@platejs/basic-nodes';
    import {
      BoldPlugin,
      ItalicPlugin,
      UnderlinePlugin,
    } from '@platejs/basic-nodes/react';
     
    export const BasicMarkInputRulesKit = [
      BoldPlugin.configure({
        inputRules: [
          BoldRules.markdown({ variant: '*' }),
          BoldRules.markdown({ variant: '_' }),
        ],
      }),
      ItalicPlugin.configure({
        inputRules: [
          ItalicRules.markdown({ variant: '*' }),
          ItalicRules.markdown({ variant: '_' }),
        ],
      }),
      UnderlinePlugin.configure({ inputRules: [UnderlineRules.markdown()] }),
    ] as const;
    import {
      BoldRules,
      ItalicRules,
      UnderlineRules,
    } from '@platejs/basic-nodes';
    import {
      BoldPlugin,
      ItalicPlugin,
      UnderlinePlugin,
    } from '@platejs/basic-nodes/react';
     
    export const BasicMarkInputRulesKit = [
      BoldPlugin.configure({
        inputRules: [
          BoldRules.markdown({ variant: '*' }),
          BoldRules.markdown({ variant: '_' }),
        ],
      }),
      ItalicPlugin.configure({
        inputRules: [
          ItalicRules.markdown({ variant: 
    
    
    
    
    

    Register both * and _ variants to accept **bold** and __bold__. Underline uses a fixed __x__ form, so its factory takes no options.

    Combos. MarkComboRules.markdown() is a single factory that covers multi-delimiter patterns like ***bold italic***. Register combos alongside the single-mark rules on the plugin that owns the dominant mark.

    import { BoldRules, MarkComboRules } from '@platejs/basic-nodes';
    import { BoldPlugin } from '@platejs/basic-nodes/react';
     
    export const ComboInputRulesPlugin = 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' }),
      ],
    });
    import { BoldRules, MarkComboRules } from '@platejs/basic-nodes';
    import { BoldPlugin } from '@platejs/basic-nodes/react';
     
    export const ComboInputRulesPlugin = 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' }),
      ],
    });

    Combo variants: 'boldItalic' | 'boldUnderline' | 'boldItalicUnderline' | 'italicUnderline'.

    Inline code, strikethrough, subscript, superscript, highlight.

    import {
      CodeRules,
      HighlightRules,
      ScriptRules,
      StrikethroughRules,
    } from '@platejs/basic-nodes';
    import {
      CodePlugin,
      HighlightPlugin,
      ScriptPlugin,
      StrikethroughPlugin,
    } from '@platejs/basic-nodes/react';
     
    export const AdditionalMarkInputRulesKit = [
      CodePlugin.configure({ inputRules: [CodeRules.markdown()] }),
      StrikethroughPlugin.configure({
        inputRules: [StrikethroughRules.markdown()],
      }),
      ScriptPlugin.configure({
        inputRules: [
          ScriptRules.markdown({ value: 'sub' }),
          ScriptRules.markdown({ value: 'sup' }),
        ],
      }),
      HighlightPlugin.configure({
        inputRules: [
          HighlightRules.markdown({ variant: '==' }),
          HighlightRules.markdown({ variant: '≡' }),
        ],
      }),
    ] as const;
    import {
      CodeRules,
      HighlightRules,
      ScriptRules,
      StrikethroughRules,
    } from '@platejs/basic-nodes';
    import {
      CodePlugin,
      HighlightPlugin,
      ScriptPlugin,
      StrikethroughPlugin,
    } from '@platejs/basic-nodes/react';
     
    export const AdditionalMarkInputRulesKit = [
      CodePlugin.configure({ inputRules: [CodeRules.markdown()] }),
      StrikethroughPlugin.configure({
        inputRules: [StrikethroughRules.markdown()],
      }),
      ScriptPlugin.configure({
        inputRules: [
          ScriptRules.markdown({ value: 'sub'
    
    
    
    
    
    
    
    
    
    

    Inline code uses `x`. Strikethrough uses ~~x~~. Subscript is ~x~. Superscript is ^x^. Highlight accepts ==x== or ≡x≡ — pick either, both, or pass a different variant.

    Code Blocks

    Code blocks are fenced, which makes them different from simple marks or block prefixes. They ship as a block fence rule, and you must pass on to pick when the fence commits.

    import { CodeBlockRules } from '@platejs/code-block';
    import { CodeBlockPlugin } from '@platejs/code-block/react';
     
    export const ImmediateCodeBlockPlugin = CodeBlockPlugin.configure({
      inputRules: [CodeBlockRules.markdown({ on: 'match' })],
    });
    import { CodeBlockRules } from '@platejs/code-block';
    import { CodeBlockPlugin } from '@platejs/code-block/react';
     
    export const ImmediateCodeBlockPlugin = CodeBlockPlugin.configure({
      inputRules: [CodeBlockRules.markdown({ on: 'match' })],
    });

    on: 'match' commits the moment the fence text becomes complete — typing the third backtick of the opening fence converts the paragraph to a code block immediately.

    export const DeferredCodeBlockPlugin = CodeBlockPlugin.configure({
      inputRules: [CodeBlockRules.markdown({ on: 'break' })],
    });
    export const DeferredCodeBlockPlugin = CodeBlockPlugin.configure({
      inputRules: [CodeBlockRules.markdown({ on: 'break' })],
    });

    on: 'break' waits for you to press Enter after the fence is complete. Same matcher, different commit point.

    Why on is required. match and break are meaningfully different UX choices — instant vs deferred. The factory refuses to guess which one you want.

    If you need to suppress code blocks inside a specific context, pass enabled:

    const codeBlockRule = CodeBlockRules.markdown({
      on: 'match',
      enabled: ({ editor }) => !isInsideSomeCustomContainer(editor),
    });
    const codeBlockRule = CodeBlockRules.markdown({
      on: 'match',
      enabled: ({ editor }) => !isInsideSomeCustomContainer(editor),
    });

    Lists

    List rules live in @platejs/list and register on the single ListPlugin. Each factory targets one shape.

    import {
      BulletedListRules,
      OrderedListRules,
      TaskListRules,
    } from '@platejs/list';
    import { ListPlugin } from '@platejs/list/react';
     
    export const ListInputRulesPlugin = ListPlugin.configure({
      inputRules: [
        BulletedListRules.markdown({ variant: '-' }),
        BulletedListRules.markdown({ variant: '*' }),
        OrderedListRules.markdown({ variant: '.' }),
        OrderedListRules.markdown({ variant: ')' }),
        TaskListRules.markdown({ checked: false }),
        TaskListRules.markdown({ checked: true }),
      ],
    });
    import {
      BulletedListRules,
      OrderedListRules,
      TaskListRules,
    } from '@platejs/list';
    import { ListPlugin } from '@platejs/list/react';
     
    export const ListInputRulesPlugin = ListPlugin.configure({
      inputRules: [
        BulletedListRules.markdown({ variant: '-' }),
        BulletedListRules.markdown({ variant: '*' }),
        OrderedListRules.markdown({ variant: '.' }),
        OrderedListRules.markdown({ variant: ')' }),
        TaskListRules.markdown({ checked: false }),
        TaskListRules.markdown({ checked: true }),
    
    
    • BulletedListRules.markdown({ variant }) accepts '-' or '*'.
    • OrderedListRules.markdown({ variant }) accepts '.' or ')'. The rule parses the leading number and starts the list from it, so 3. creates a list that begins at 3.
    • TaskListRules.markdown({ checked }) maps [] to an unchecked task and [x] to a checked task.

    All list rules use enabled to opt out inside code blocks.

    Math

    Math has two shapes: inline $x$ and block $$x$$. They're intentionally split so apps can enable one without the other.

    import { MathRules } from '@platejs/math';
    import { EquationPlugin, InlineEquationPlugin } from '@platejs/math/react';
     
    export const MathInputRulesKit = [
      InlineEquationPlugin.configure({
        inputRules: [MathRules.markdown({ variant: '$' })],
      }),
      EquationPlugin.configure({
        inputRules: [MathRules.markdown({ variant: '$$', on: 'break' })],
      }),
    ] as const;
    import { MathRules } from '@platejs/math';
    import { EquationPlugin, InlineEquationPlugin } from '@platejs/math/react';
     
    export const MathInputRulesKit = [
      InlineEquationPlugin.configure({
        inputRules: [MathRules.markdown({ variant: '$' })],
      }),
      EquationPlugin.configure({
        inputRules: [MathRules.markdown({ variant: '$$', on: 'break' })],
      }),
    ] as const;

    variant: '$' is inline — a delimited mark rule. variant: '$$' is a block fence, so — like CodeBlockRules — it takes on: 'match' | 'break'. The example uses on: 'break', which commits the block equation when you press Enter after the fence.

    Links

    Link rules are not just substitutions — they validate URLs and wrap text with a full link node.

    import { LinkRules } from '@platejs/link';
    import { LinkPlugin } from '@platejs/link/react';
     
    export const LinkInputRulesPlugin = LinkPlugin.configure({
      inputRules: [
        LinkRules.markdown(),
        LinkRules.autolink({ variant: 'paste' }),
        LinkRules.autolink({ variant: 'space' }),
        LinkRules.autolink({ variant: 'break' }),
      ],
    });
    import { LinkRules } from '@platejs/link';
    import { LinkPlugin } from '@platejs/link/react';
     
    export const LinkInputRulesPlugin = LinkPlugin.configure({
      inputRules: [
        LinkRules.markdown(),
        LinkRules.autolink({ variant: 'paste' }),
        LinkRules.autolink({ variant: 'space' }),
        LinkRules.autolink({ variant: 'break' }),
      ],
    });
    • LinkRules.markdown() handles [label](https://...).
    • LinkRules.autolink({ variant: 'paste' }) turns a pasted URL into a link.
    • LinkRules.autolink({ variant: 'space' }) detects a URL when you type a trailing space.
    • LinkRules.autolink({ variant: 'break' }) detects a URL when you press Enter.

    Register whichever variants you want — you can pick one, two, or all four.

    That's the full feature-owned catalog. Every package you add contributes its own rules; the editor picks them up the moment you register them.

    Local Copied Shortcuts

    Sometimes you want small text substitutions — smart quotes, arrows, fractions, trademarks — that don't belong to any feature package. Use createTextSubstitutionInputRule for this.

    createTextSubstitutionInputRule

    The helper takes a patterns array and builds a single insertText rule. Each pattern has a match (what you type) and a format (what you end up with).

    import {
      defineBasePlugin,
      createTextSubstitutionInputRule,
      PLUGINS,
    } from 'platejs';
     
    const arrowsRule = createTextSubstitutionInputRule({
      enabled: ({ editor }) => {
        const codeBlock = editor.plugin(PLUGINS.codeBlock);
     
        return (
          !codeBlock.installed ||
          !editor.read.nodes.some({ type: codeBlock.schema.type })
        );
      },
      patterns: [
        { format: '→', match: '->' },
        { format: '←', match: '<-' },
        { format: '⇒', match: '=>' },
        { format: '⇐', match: ['<=', '≤='] },
      ],
    });
     
    export const ArrowsShortcutsPlugin = defineBasePlugin('arrowsShortcuts', {
      inputRules: [arrowsRule],
    });
    import {
      defineBasePlugin,
      createTextSubstitutionInputRule,
      PLUGINS,
    } from 'platejs';
     
    const arrowsRule = createTextSubstitutionInputRule({
      enabled: ({ editor }) => {
        const codeBlock = editor.plugin(PLUGINS.codeBlock);
     
        return (
          !codeBlock.installed ||
          !editor.read.nodes.some({ type: codeBlock.schema.type })
        );
      },
      patterns: [
        { format: '→', match: '->' },
        { format: '←', match: '<-' },
    
    
    
    
    
    
    
    

    A few details worth knowing:

    • match can be a string or an array of strings — all entries are checked before formatting.
    • format can be a single string or a [open, close] tuple. A tuple wraps the typed text as open + content + close, useful for smart quotes and brackets.
    • trigger defaults to the last character of each match, which is almost always what you want. Override it only if you need a distinct commit char.
    • enabled gates the whole rule, so you don't have to guard each pattern individually.

    Use AutoformatKit As A Starting Point

    The autoformat.tsx file in Plate's registry wires up a full set of substitutions — arrows, comparisons, equalities, fractions, legal symbols, smart quotes, sub/superscript numerals — all gated against code blocks. Copy it into your app and edit the patterns.

    autoformat.tsx
    import {
      defineBasePlugin,
      createTextSubstitutionInputRule,
      PLUGINS,
    } from 'platejs';
     
    const createAutoformatTextSubstitutionRule = ({
      patterns,
    }: {
      patterns: Parameters<typeof createTextSubstitutionInputRule>[0]['patterns'];
    }) =>
      createTextSubstitutionInputRule({
        enabled: ({ editor }) => {
          const codeBlock = editor.plugin(PLUGINS.codeBlock);
     
          return (
            !codeBlock.installed ||
            !editor.read.nodes.some({ type: codeBlock.schema.type })
          );
        },
        patterns,
      });
     
    const legalRule = createAutoformatTextSubstitutionRule({
      patterns: [
        { format: '™', match: ['(tm)', '(TM)'] },
        { format: '®', match: ['(r)', '(R)'] },
        { format: '©', match: ['(c)', '(C)'] },
      ],
    });
     
    const smartQuotesRule = createAutoformatTextSubstitutionRule({
      patterns: [
        { format: ['“', '”'], match: '"' },
        { format: ['‘', '’'], match: "'" },
      ],
    });
     
    const AutoformatShortcutsPlugin = defineBasePlugin('autoformatShortcuts', {
      inputRules: [legalRule, smartQuotesRule],
    });
     
    export const AutoformatKit = [AutoformatShortcutsPlugin];
    autoformat.tsx
    import {
      defineBasePlugin,
      createTextSubstitutionInputRule,
      PLUGINS,
    } from 'platejs';
     
    const createAutoformatTextSubstitutionRule = ({
      patterns,
    }: {
      patterns: Parameters<typeof createTextSubstitutionInputRule>[0]['patterns'];
    }) =>
      createTextSubstitutionInputRule({
        enabled: ({ editor }) => {
          const codeBlock = editor.plugin(PLUGINS.codeBlock);
     
          return (
            !codeBlock.installed ||
            !
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    

    Use copied code for local substitutions. Author project-specific substitutions with feature-owned inputRules in copied registry code or your own local kit so the rules stay visible and editable.

    When To Reach For defineInputRule

    Text substitution covers the glyph-for-glyph case. When you need more — reading the current block, dispatching a richer transform, or detecting a pattern that isn't a simple character match — drop down to defineInputRule or one of the low-level builders.

    Custom Rules

    Everything above is sugar on top of three low-level authoring surfaces:

    • defineInputRule, an identity function that types a raw rule inline.
    • The specialized builders: createMarkInputRule, createBlockStartInputRule, createBlockFenceInputRule, createTextSubstitutionInputRule.
    • createRuleFactory, the package-authoring helper used to expose semantic families like MathRules.markdown(...) or LinkRules.autolink(...) without re-declaring shared runtime fields.

    Use this section when none of the shipped families fit your need or when you're authoring your own reusable rule family.

    Register Explicit Rule Instances

    Rules are concrete objects. You pass them into inputRules exactly as-is. To override a field like priority or enabled on a finished instance, spread the rule and replace the field:

    import { HeadingRules } from '@platejs/basic-nodes';
    import { LinkRules } from '@platejs/link';
    import { HeadingPlugin } from '@platejs/basic-nodes/react';
    import { LinkPlugin } from '@platejs/link/react';
     
    export const CustomInputRulesKit = [
      HeadingPlugin.configure({ inputRules: [HeadingRules.markdown()] }),
      LinkPlugin.configure({
        inputRules: [
          LinkRules.markdown(),
          { ...LinkRules.autolink({ variant: 'paste' }), priority: 200 },
        ],
      }),
    ] as const;
    import { HeadingRules } from '@platejs/basic-nodes';
    import { LinkRules } from '@platejs/link';
    import { HeadingPlugin } from '@platejs/basic-nodes/react';
    import { LinkPlugin } from '@platejs/link/react';
     
    export const CustomInputRulesKit = [
      HeadingPlugin.configure({ inputRules: [HeadingRules.markdown()] }),
      LinkPlugin.configure({
        inputRules: [
          LinkRules.markdown(),
          { ...LinkRules.autolink({ variant: 'paste' }), priority: 200 },
        ],
      }),
    ] as const;

    Why the spread? Package factories are intentionally narrow — most don't take a priority option. Overriding is the caller's job, and spreading the returned rule keeps it obvious that you're changing one field on an already-built instance.

    The same pattern works for enabled. If a family ships with a sensible default but you need stricter gating in one app, spread the rule and override:

    const gatedBlockquoteRule = {
      ...BlockquoteRules.markdown(),
      enabled: ({ editor }) => !isInsideCallout(editor),
    };
    const gatedBlockquoteRule = {
      ...BlockquoteRules.markdown(),
      enabled: ({ editor }) => !isInsideCallout(editor),
    };

    Plugin-Side Factories

    For plugin authors, the inputRules option also accepts a function that receives a rule builder. Use this form when your rules depend on plugin-local state or when you want your rule wiring to live next to the plugin's types.

    import { BaseCodeBlockPlugin } from '@platejs/code-block';
    import { defineBasePlugin } from 'platejs';
     
    const CustomPlugin = defineBasePlugin('custom', {
      dependencies: [BaseCodeBlockPlugin],
      inputRules: ({ rule }) => [
        rule.mark({
          trigger: '*',
          start: '*',
        }),
        rule.blockStart({
          trigger: ' ',
          match: '>',
          mode: 'wrap',
          node: 'blockquote',
        }),
        rule.blockFence({
          fence: '```',
          on: 'match',
          apply: (context, match) => {
            context.editor.update((tx) => {
              tx.text.delete({ at: match.range });
              tx.nodes.set(
                {
                  type: context.editor.plugin(BaseCodeBlockPlugin).schema.type,
                },
                { at: match.path }
              );
            });
          },
        }),
      ],
    });
    import { BaseCodeBlockPlugin } from '@platejs/code-block';
    import { defineBasePlugin } from 'platejs';
     
    const CustomPlugin = defineBasePlugin('custom', {
      dependencies: [BaseCodeBlockPlugin],
      inputRules: ({ rule }) => [
        rule.mark({
          trigger: '*',
          start: '*',
        }),
        rule.blockStart({
          trigger: ' ',
          match: '>',
          mode: 'wrap',
          node: 'blockquote',
        }),
        rule.blockFence
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    

    The rule builder exposes six primitives:

    MethodWraps
    rule.mark(config)createMarkInputRule
    rule.blockStart(config)createBlockStartInputRule
    rule.blockFence(config)createBlockFenceInputRule
    rule.insertText(rule)typed defineInputRule for insertText
    rule.insertBreak(rule)typed defineInputRule for insertBreak
    rule.insertData(rule)typed defineInputRule for insertData

    Use the factory form when you need that co-location; use the array form when you just want to register a handful of concrete instances.

    Author A Rule Family

    When you're shipping a package, you usually want a public factory like MyNodeRules.markdown(...) that takes a narrow options object and hides the low-level rule plumbing. Reach for createRuleFactory.

    import { createRuleFactory, PLUGINS } from 'platejs';
     
    export const BlockquoteRules = {
      markdown: createRuleFactory<{}, { marker: string }>({
        type: 'blockStart',
        marker: '>',
        trigger: ' ',
        mode: 'wrap',
        match: ({ marker }) => marker,
        enabled: ({ editor }) => {
          const codeBlock = editor.plugin(PLUGINS.codeBlock);
     
          return (
            !codeBlock.installed ||
            !editor.read.nodes.some({ type: codeBlock.schema.type })
          );
        },
      }),
    };
    import { createRuleFactory, PLUGINS } from 'platejs';
     
    export const BlockquoteRules = {
      markdown: createRuleFactory<{}, { marker: string }>({
        type: 'blockStart',
        marker: '>',
        trigger: ' ',
        mode: 'wrap',
        match: ({ marker }) => marker,
        enabled: ({ editor }) => {
          const codeBlock = editor.plugin(PLUGINS.codeBlock);
     
          return (
            !codeBlock.installed ||
            !
    
    
    
    

    A few things to notice:

    • type picks the underlying builder. Use 'mark', 'blockStart', 'blockFence', or 'textSubstitution'.
    • The two generic parameters model your public API: TRequired (the first) are options callers must pass; TDefaults (the second) are options you supply a default for — here, marker: '>'.
    • Factory values can be plain values ('>') or functions of the input (({ marker }) => marker). The input includes the runtime context, your defaults, and any required options the caller passes.
    • For blockStart, core already owns the base match payload: { range, text }. If you provide resolveMatch, return only your extra fields — core merges them onto the base payload before apply runs.
    • enabled and priority stay available as runtime overrides on the returned rule instance — callers can override them even when your factory sets a default.

    That's the pattern every *Rules.markdown(...) family in Plate uses. Clone it when you need your own.

    Done. Between defineInputRule, the four specialized builders, the plugin-side rule builder, and createRuleFactory, every rule in Plate — yours included — comes from the same small core.

    How Rule Execution Works

    Input rules run inside the insertText, insertBreak, and insertData transforms. For each call, the runtime walks every registered rule for that target in priority order, and the first rule that passes enabled and produces a non-undefined resolve gets to call apply.

    Targets

    A rule's target field picks which lane it runs in.

    TargetFires on
    insertTextEach character typed into the editor
    insertBreakEach time the user presses Enter
    insertDataEach time data is pasted or dropped

    The high-level factories set target for you:

    • createMarkInputRule and createBlockStartInputRule always produce insertText rules.
    • createBlockFenceInputRule produces an insertText rule when on: 'match' and an insertBreak rule when on: 'break'.
    • createTextSubstitutionInputRule always produces an insertText rule.

    defineInputRule lets you pick any target by setting the target field directly.

    Selection Context

    Every enabled, resolve, and apply call receives a context object with the live editor, the selection state, and a handful of lazy helpers.

    FieldReturns
    editorThe current BaseEditor
    isCollapsedWhether the selection is collapsed
    pluginThe descriptor that owns the rule; use editor.plugin(plugin).schema for persisted identity.
    getBlockEntry()The current block's NodeEntry, or undefined
    getBlockStartRange()The range from block start to current selection
    getBlockStartText()The text from block start to current selection
    getBlockTextBeforeSelection()The text in the current block before the cursor
    getCharBefore()The character immediately before the cursor
    getCharAfter()The character immediately after the cursor

    The get* helpers are memoized — calling them twice inside the same rule evaluation doesn't recompute. That matters because multiple rules can share the same evaluation pass.

    Target-specific context fields:

    • insertText rules additionally receive text, cause: 'insertText', and an insertText callback for default fallthrough.
    • insertBreak rules receive cause: 'insertBreak' and an insertBreak callback.
    • insertData rules receive data: DataTransfer, text, cause: 'insertData', and an insertData callback.

    Lifecycle

    For each transform call, the runtime walks rules in priority order (highest first). For each rule, it runs the following steps:

    1. enabled. A boolean gate. If it returns false, skip to the next rule.
    2. resolve. Computes a match payload. If it returns undefined, skip to the next rule.
    3. apply. Performs the transform. If it returns false, the runtime treats the rule as not consumed and continues; any other return value consumes the input and short-circuits the rest of the walk.

    If no rule consumes the input, the default Plite transform runs as normal.

    FieldPurpose
    triggerRestricts an insertText rule to fire only when the typed character matches (string or array)
    enabledPolicy gate, evaluated first
    resolveComputes the match payload passed to apply
    applyPerforms the transform
    prioritySort order for rules on the same target
    onBlock-fence commit mode: 'match' or 'break'
    mimeTypesNarrows an insertData rule to specific MIME types

    Use enabled for policy, not match. The matcher should own the syntax of a rule (what pattern counts as a hit). Gating — "don't fire in code blocks", "only fire when this plugin is active" — belongs in enabled. Returning undefined from resolve just to suppress a rule works, but it hides intent and makes rules harder to compose.

    API Reference

    Low-level surface. Reach for this when the high-level factories don't cover your case. Everything below is exported from platejs.

    Rule Targets

    type InputRuleTarget = 'insertText' | 'insertBreak' | 'insertData';
    type InputRuleTarget = 'insertText' | 'insertBreak' | 'insertData';

    defineInputRule

    Identity function that types a rule inline.

    function defineInputRule<TRule extends InputRule>(rule: TRule): TRule;
    function defineInputRule<TRule extends InputRule>(rule: TRule): TRule;
    import { defineInputRule } from 'platejs';
     
    const copyrightRule = defineInputRule({
      target: 'insertText',
      trigger: ')',
      resolve: (context) => {
        if (context.text !== ')') return;
        if (!context.getBlockTextBeforeSelection().endsWith('(c')) return;
     
        return { replacement: '©' };
      },
      apply: ({ editor }, match) => {
        editor.update((tx) => {
          tx.text.delete({ distance: 2, reverse: true, unit: 'character' });
          tx.text.insert(match.replacement);
        });
      },
    });
    import { defineInputRule } from 'platejs';
     
    const copyrightRule = defineInputRule({
      target: 'insertText',
      trigger: ')',
      resolve: (context) => {
        if (context.text !== ')') return;
        if (!context.getBlockTextBeforeSelection().endsWith('(c')) return;
     
        return { replacement: '©' };
      },
      apply: ({ editor }, match) => {
        editor.update((tx) 
    
    
    
    
    

    Use it when you want a raw rule object typed against InsertTextInputRule, InsertBreakInputRule, or InsertDataInputRule without going through a builder.

    createRuleFactory

    Package-facing helper for semantic rule families. Use it when you want to expose a narrow public factory like BlockquoteRules.markdown(...), keep shared runtime fields like enabled and priority, and hide the low-level rule construction details.

    import { createRuleFactory, PLUGINS } from 'platejs';
     
    export const BlockquoteRules = {
      markdown: createRuleFactory<{}, { marker: string }>({
        type: 'blockStart',
        marker: '>',
        trigger: ' ',
        match: ({ marker }) => marker,
        mode: 'wrap',
        enabled: ({ editor }) => {
          const codeBlock = editor.plugin(PLUGINS.codeBlock);
     
          return (
            !codeBlock.installed ||
            !editor.read.nodes.some({ type: codeBlock.schema.type })
          );
        },
      }),
    };
    import { createRuleFactory, PLUGINS } from 'platejs';
     
    export const BlockquoteRules = {
      markdown: createRuleFactory<{}, { marker: string }>({
        type: 'blockStart',
        marker: '>',
        trigger: ' ',
        match: ({ marker }) => marker,
        mode: 'wrap',
        enabled: ({ editor }) => {
          const codeBlock = editor.plugin(PLUGINS.codeBlock);
     
          return (
            !codeBlock.installed ||
            !
    
    
    
    

    The returned function is your public rule family. Concrete values in the config become default public options (marker: '>'), while the generic type parameters let you model required options and defaults for the family. The created rule instance still supports the shared runtime overrides: enabled and priority.

    createMarkInputRule

    Delimited inline marks (**bold**, `code`, ~~strike~~).

    function createMarkInputRule(config: {
      start: string;
      end?: string;
      trigger: string;
      mark?: PluginReference | string;
      marks?: readonly (PluginReference | string)[];
      trim?: 'allow' | 'reject';
      enabled?: (context: InsertTextInputRuleContext) => boolean;
      priority?: number;
    }): InsertTextInputRule;
    function createMarkInputRule(config: {
      start: string;
      end?: string;
      trigger: string;
      mark?: PluginReference | string;
      marks?: readonly (PluginReference | string)[];
      trim?: 'allow' | 'reject';
      enabled?: (context: InsertTextInputRuleContext) => boolean;
      priority?: number;
    }): InsertTextInputRule;
    import { createMarkInputRule } from 'platejs';
     
    createMarkInputRule({
      start: '**',
      end: '*',
      trigger: '*',
    });
    import { createMarkInputRule } from 'platejs';
     
    createMarkInputRule({
      start: '**',
      end: '*',
      trigger: '*',
    });

    start is the opening delimiter. end is an optional closing delimiter; when omitted, the rule does not look for a separate closing delimiter before the trigger. trigger is the character that commits the match. trim: 'reject' refuses spans with leading or trailing whitespace. mark and marks restrict which mark(s) the rule applies.

    createBlockStartInputRule

    Block-start patterns typed at the beginning of a block (# , > , - , 1. ).

    function createBlockStartInputRule<TMatch extends object = {}>(config: {
      trigger: string;
      match:
        | RegExp
        | string
        | ((context: InsertTextInputRuleContext) => RegExp | string | undefined);
      mode?: 'set' | 'toggle' | 'wrap';
      node?: PluginReference | string;
      removeMatchedText?: boolean;
      resolveMatch?: (args: {
        match: RegExpMatchArray | string;
        range: Range;
        text: string;
      }) => TMatch | undefined;
      apply?: (
        context: InsertTextInputRuleContext,
        match: BlockStartInputRuleMatch & TMatch
      ) => boolean | void;
      enabled?: (context: InsertTextInputRuleContext) => boolean;
      priority?: number;
    }): InsertTextInputRule<TMatch>;
    function createBlockStartInputRule<TMatch extends object = {}>(config: {
      trigger: string;
      match:
        | RegExp
        | string
        | ((context: InsertTextInputRuleContext) => RegExp | string | undefined);
      mode?: 'set' | 'toggle' | 'wrap';
      node?: PluginReference | string;
      removeMatchedText?: boolean;
      resolveMatch?: (args: {
        match: RegExpMatchArray
    
    
    
    
    
    
    
    
    
    
    import { createBlockStartInputRule } from 'platejs';
     
    createBlockStartInputRule({
      trigger: ' ',
      match: '>',
      mode: 'wrap',
    });
    import { createBlockStartInputRule } from 'platejs';
     
    createBlockStartInputRule({
      trigger: ' ',
      match: '>',
      mode: 'wrap',
    });
    • trigger is the commit character — typically ' '.
    • match is the block-start text: a string, a RegExp, or a function that returns one based on context.
    • mode picks the transform: 'set' replaces the block type, 'toggle' flips it, 'wrap' wraps the block in a new element.
    • node is an installed element plugin descriptor or dynamic plugin name used by mode.
    • apply overrides the built-in transform entirely — supply your own when none of the modes fit. That also means you own matched-text cleanup; if you still want the shorthand removed, delete match.range yourself.
    • resolveMatch returns extra fields only. Core still provides the base { range, text } payload automatically, and apply receives the merged object.

    createBlockFenceInputRule

    Fenced block patterns like triple-backtick code fences or $$ math fences.

    function createBlockFenceInputRule<TMatch>(config: {
      fence: string;
      on: 'break' | 'match';
      apply: (context: SelectionInputRuleContext, match: TMatch) => boolean | void;
      block?: PluginReference | string;
      resolveMatch?: (args: {
        fence: string;
        path: Path;
        range: Range;
        text: string;
      }) => TMatch | undefined;
      enabled?: (context: SelectionInputRuleContext) => boolean;
      priority?: number;
    }): InsertTextInputRule<TMatch> | InsertBreakInputRule<TMatch>;
    function createBlockFenceInputRule<TMatch>(config: {
      fence: string;
      on: 'break' | 'match';
      apply: (context: SelectionInputRuleContext, match: TMatch) => boolean | void;
      block?: PluginReference | string;
      resolveMatch?: (args: {
        fence: string;
        path: Path;
        range: Range;
        text: string;
    
    
    
    
    import { BaseCodeBlockPlugin } from '@platejs/code-block';
    import { createBlockFenceInputRule } from 'platejs';
     
    createBlockFenceInputRule({
      fence: '```',
      on: 'match',
      apply: (context, match) => {
        const codeBlock = context.editor.plugin(BaseCodeBlockPlugin);
     
        if (!codeBlock.installed) return false;
     
        context.editor.update((tx) => {
          tx.text.delete({ at: match.range });
          tx.nodes.set(
            { type: codeBlock.schema.type },
            { at: match.path }
          );
        });
      },
    });
    import { BaseCodeBlockPlugin } from '@platejs/code-block';
    import { createBlockFenceInputRule } from 'platejs';
     
    createBlockFenceInputRule({
      fence: '```',
      on: 'match',
      apply: (context, match) => {
        const codeBlock = context.editor.plugin(BaseCodeBlockPlugin);
     
        if (!codeBlock.installed) return false;
     
        context.editor.update((tx) => {
          tx.text.delete({ at: match.range });
          tx.nodes.set(
            { type: codeBlock.schema.type },
    
    
    
    
    

    on: 'match' commits when the fence becomes complete inside the current paragraph. on: 'break' commits when Enter is pressed after the fence is complete — useful when the user may want to type more before committing.

    The runtime owns the matcher: it checks that the selection is collapsed, the cursor is at the block's end, and the block text equals fence. If you pass block, the matcher also requires that installed plugin's element type. You own apply, which performs the replacement. The returned rule targets insertText when on: 'match' and insertBreak when on: 'break'.

    createTextSubstitutionInputRule

    Glyph-for-glyph substitutions.

    function createTextSubstitutionInputRule(config: {
      patterns: Array<{
        format: readonly [string, string] | string;
        match: readonly string[] | string;
        trigger?: readonly string[] | string;
      }>;
      enabled?: (context: InsertTextInputRuleContext) => boolean;
      priority?: number;
    }): InsertTextInputRule;
    function createTextSubstitutionInputRule(config: {
      patterns: Array<{
        format: readonly [string, string] | string;
        match: readonly string[] | string;
        trigger?: readonly string[] | string;
      }>;
      enabled?: (context: InsertTextInputRuleContext) => boolean;
      priority?: number;
    }): InsertTextInputRule;
    import { createTextSubstitutionInputRule } from 'platejs';
     
    createTextSubstitutionInputRule({
      patterns: [
        { format: '→', match: '->' },
        { format: ['«', '»'], match: '<<' },
      ],
    });
    import { createTextSubstitutionInputRule } from 'platejs';
     
    createTextSubstitutionInputRule({
      patterns: [
        { format: '→', match: '->' },
        { format: ['«', '»'], match: '<<' },
      ],
    });

    format is either a replacement string or a [open, close] tuple that wraps the typed content. match is a string or array of strings that trigger the replacement. trigger defaults to the last character of each match and rarely needs overriding.

    matchDelimitedInline

    Low-level matcher used under createMarkInputRule. Returns a { content, deleteRange } match for a delimited inline pattern, or undefined.

    import { matchDelimitedInline } from 'platejs';
     
    const match = matchDelimitedInline(context, {
      open: '**',
      close: '*',
      requireClosingDelimiter: true,
      trim: 'reject',
    });
    import { matchDelimitedInline } from 'platejs';
     
    const match = matchDelimitedInline(context, {
      open: '**',
      close: '*',
      requireClosingDelimiter: true,
      trim: 'reject',
    });

    Use it when you're authoring a custom insertText rule that needs the same matching shape as a mark without going through createMarkInputRule.

    matchBlockStart

    The block-start matcher takes the current context plus a matcher config and returns a match payload or undefined. Use it when you want the matcher logic without the factory's apply wiring. Fence matching stays owned by createBlockFenceInputRule.

    import { matchBlockStart } from 'platejs';
     
    const startMatch = matchBlockStart(context, { match: '>' });
    import { matchBlockStart } from 'platejs';
     
    const startMatch = matchBlockStart(context, { match: '>' });

    Package Rule Families

    Every family below is a single export from its package. Each .markdown() (or .autolink()) call returns a concrete rule you pass into the matching plugin's inputRules.

    FamilyPackageDescription
    HeadingRules@platejs/basic-nodesMarkdown prefix rules for resolved persisted types h1 through h6
    BlockquoteRules@platejs/basic-nodes> block-wrap rule, gated out of code blocks
    HorizontalRuleRules@platejs/basic-nodes--- and ___ variant rules
    BoldRules@platejs/basic-nodes**x** / __x__ mark rule
    ItalicRules@platejs/basic-nodes*x* / _x_ mark rule
    UnderlineRules@platejs/basic-nodes__x__ mark rule
    CodeRules@platejs/basic-nodes`x` inline code mark rule
    StrikethroughRules@platejs/basic-nodes~~x~~ mark rule
    ScriptRules@platejs/basic-nodes~x~ for 'sub', ^x^ for 'sup'
    HighlightRules@platejs/basic-nodes==x== / ≡x≡ mark rule
    MarkComboRules@platejs/basic-nodesMulti-mark combo rules (bold/italic/underline)
    CodeBlockRules@platejs/code-blockTriple-backtick block fence rule, requires on
    BulletedListRules@platejs/list- / * bulleted list rule
    OrderedListRules@platejs/list1. / 1) ordered list rule, preserves start number
    TaskListRules@platejs/list[] / [x] task list rule
    MathRules@platejs/mathInline $x$ and block $$x$$ rules
    LinkRules@platejs/linkMarkdown [label](url) and autolink rules

    Each family's factory takes a narrow options object — see the per-section examples above for the exact shape.

    LinkKit,
    ...MathKit,
    ],
    });
    ({ inputRules: [HeadingRules.
    markdown
    ()] }),
    ] as const;
    '*'
    }),
    ItalicRules.markdown({ variant: '_' }),
    ],
    }),
    UnderlinePlugin.configure({ inputRules: [UnderlineRules.markdown()] }),
    ] as const;
    }),
    ScriptRules.markdown({ value: 'sup' }),
    ],
    }),
    HighlightPlugin.configure({
    inputRules: [
    HighlightRules.markdown({ variant: '==' }),
    HighlightRules.markdown({ variant: '≡' }),
    ],
    }),
    ] as const;
    ],
    });
    { format: '⇒', match: '=>' },
    { format: '⇐', match: ['<=', '≤='] },
    ],
    });
    export const ArrowsShortcutsPlugin = defineBasePlugin('arrowsShortcuts', {
    inputRules: [arrowsRule],
    });
    editor.read.nodes.
    some
    ({ type: codeBlock.schema.type })
    );
    },
    patterns,
    });
    const legalRule = createAutoformatTextSubstitutionRule({
    patterns: [
    { format: '™', match: ['(tm)', '(TM)'] },
    { format: '®', match: ['(r)', '(R)'] },
    { format: '©', match: ['(c)', '(C)'] },
    ],
    });
    const smartQuotesRule = createAutoformatTextSubstitutionRule({
    patterns: [
    { format: ['“', '”'], match: '"' },
    { format: ['‘', '’'], match: "'" },
    ],
    });
    const AutoformatShortcutsPlugin = defineBasePlugin('autoformatShortcuts', {
    inputRules: [legalRule, smartQuotesRule],
    });
    export const AutoformatKit = [AutoformatShortcutsPlugin];
    ({
    fence: '```',
    on: 'match',
    apply: (context, match) => {
    context.editor.update((tx) => {
    tx.text.delete({ at: match.range });
    tx.nodes.set(
    {
    type: context.editor.plugin(BaseCodeBlockPlugin).schema.type,
    },
    { at: match.path }
    );
    });
    },
    }),
    ],
    });
    editor.read.nodes.
    some
    ({ type: codeBlock.schema.type })
    );
    },
    }),
    };
    =>
    {
    tx.text.delete({ distance: 2, reverse: true, unit: 'character' });
    tx.text.insert(match.replacement);
    });
    },
    });
    editor.read.nodes.
    some
    ({ type: codeBlock.schema.type })
    );
    },
    }),
    };
    |
    string
    ;
    range: Range;
    text: string;
    }) => TMatch | undefined;
    apply?: (
    context: InsertTextInputRuleContext,
    match: BlockStartInputRuleMatch & TMatch
    ) => boolean | void;
    enabled?: (context: InsertTextInputRuleContext) => boolean;
    priority?: number;
    }): InsertTextInputRule<TMatch>;
    })
    =>
    TMatch
    |
    undefined
    ;
    enabled?: (context: SelectionInputRuleContext) => boolean;
    priority?: number;
    }): InsertTextInputRule<TMatch> | InsertBreakInputRule<TMatch>;
    { at: match.path }
    );
    });
    },
    });