Skip to main content
Input rules allow you to automatically modify content when specific patterns are typed in the editor. They’re perfect for creating shortcuts like converting **text** to bold or # to a heading.

Overview

ProseKit provides several types of input rules:
  • Mark Input Rules: Apply inline marks when patterns are typed
  • Text Block Input Rules: Convert text blocks to different node types
  • Wrapping Input Rules: Wrap text blocks in container nodes
  • Custom Input Rules: Define fully custom input rule behavior

Mark Input Rules

Apply inline marks like bold, italic, or code when specific patterns are typed.

Basic Usage

import { defineMarkInputRule } from '@prosekit/extensions/input-rule'

const extension = defineMarkInputRule({
  regex: /\*\*([^\s*]|[^\s*][^*]*[^\s*])\*\*$/,
  type: 'bold',
})

With Dynamic Attributes

import { defineMarkInputRule } from '@prosekit/extensions/input-rule'

const extension = defineMarkInputRule({
  regex: /~([^~]+)~$/,
  type: 'highlight',
  attrs: (match) => {
    // Extract color from pattern like ~text:yellow~
    const parts = match[1].split(':')
    return { color: parts[1] || 'yellow' }
  },
})

API Reference

regex
RegExp
required
The regular expression to match against. Should end with $ and contain exactly one capture group. Text outside the capture group will be deleted.
type
string | MarkType
required
The mark type to apply to the matched text.
attrs
Attrs | null | ((match: RegExpMatchArray) => Attrs | null)
Attributes to set on the mark. Can be a static object or a function that receives the regex match.
inCodeMark
boolean
default:"false"
Whether this rule should fire inside code marks.

Text Block Input Rules

Convert text blocks to different node types, like converting lines starting with # to headings.

Basic Usage

import { defineTextBlockInputRule } from '@prosekit/extensions/input-rule'

const extension = defineTextBlockInputRule({
  regex: /^(#{1,6})\s$/,
  type: 'heading',
  attrs: (match) => ({
    level: match[1].length,
  }),
})

API Reference

regex
RegExp
required
The regular expression to match against. Should end with $ and typically start with ^ to match only at the start of a text block.
type
string | NodeType
required
The node type to replace the matched text with.
attrs
Attrs | null | ((match: RegExpMatchArray) => Attrs | null)
Attributes to set on the node.

Wrapping Input Rules

Wrap text blocks in container nodes, like wrapping lines starting with > in blockquotes.

Basic Usage

import { defineWrappingInputRule } from '@prosekit/extensions/input-rule'

const extension = defineWrappingInputRule({
  regex: /^>\s$/,
  type: 'blockquote',
})

With Join Predicate

import { defineWrappingInputRule } from '@prosekit/extensions/input-rule'

const extension = defineWrappingInputRule({
  regex: /^-\s$/,
  type: 'bulletList',
  join: (match, node) => {
    // Only join if the previous node is also a bullet list
    return node.type.name === 'bulletList'
  },
})

API Reference

regex
RegExp
required
The regular expression to match against. Should end with $ and typically start with ^.
type
string | NodeType
required
The type of node to wrap in.
attrs
Attrs | null | ((match: RegExpMatchArray) => Attrs | null)
Attributes to set on the wrapping node.
join
(match: RegExpMatchArray, node: ProseMirrorNode) => boolean
Predicate function that controls whether to join with a similar node above. Receives the regex match and the node before the wrapped node.

Custom Input Rules

For complete control, define a custom ProseMirror input rule.
import { defineInputRule } from '@prosekit/extensions/input-rule'
import { InputRule } from '@prosekit/pm/inputrules'

const customRule = new InputRule(
  /--$/,
  (state, match, start, end) => {
    const tr = state.tr
    tr.insertText('—', start, end)
    return tr
  }
)

const extension = defineInputRule(customRule)

Real-World Examples

Markdown-Style Formatting

import { defineMarkInputRule, defineTextBlockInputRule, defineWrappingInputRule } from '@prosekit/extensions/input-rule'
import { union } from '@prosekit/core'

const markdownExtension = union([
  // Bold: **text**
  defineMarkInputRule({
    regex: /\*\*([^\s*]|[^\s*][^*]*[^\s*])\*\*$/,
    type: 'bold',
  }),
  
  // Italic: *text*
  defineMarkInputRule({
    regex: /\*([^\s*]|[^\s*][^*]*[^\s*])\*$/,
    type: 'italic',
  }),
  
  // Code: `code`
  defineMarkInputRule({
    regex: /`([^`]+)`$/,
    type: 'code',
  }),
  
  // Headings: # to ######
  defineTextBlockInputRule({
    regex: /^(#{1,6})\s$/,
    type: 'heading',
    attrs: (match) => ({ level: match[1].length }),
  }),
  
  // Blockquote: > text
  defineWrappingInputRule({
    regex: /^>\s$/,
    type: 'blockquote',
  }),
  
  // Horizontal rule: ---
  defineTextBlockInputRule({
    regex: /^---$/,
    type: 'horizontalRule',
  }),
])

Auto-Linking

import { defineMarkInputRule } from '@prosekit/extensions/input-rule'

const autoLinkExtension = defineMarkInputRule({
  regex: /(https?:\/\/[^\s]+)$/,
  type: 'link',
  attrs: (match) => ({
    href: match[1],
  }),
})

Use Cases

Markdown Shortcuts

Convert markdown syntax to formatted text as users type.

Smart Quotes

Automatically convert straight quotes to curly quotes.

Auto-Linking

Turn URLs into clickable links automatically.

Emoji Shortcuts

Convert text shortcuts like :) to emoji.

Tips

Make sure your regex patterns end with $ to match at the end of the typed text. For text block rules, start with ^ to match at the beginning.
Be careful with overlapping patterns. If multiple input rules match the same text, only the first one will fire.