Skip to main content
The search extension provides powerful search and replace capabilities with support for regex, case sensitivity, and whole word matching.

Basic Usage

import { defineSearchQuery, defineSearchCommands } from '@prosekit/extensions/search'
import { union } from '@prosekit/core'

const extension = union([
  defineSearchQuery({
    search: 'hello',
  }),
  defineSearchCommands(),
])

Search Query

Define the search query and options:
import { defineSearchQuery } from '@prosekit/extensions/search'

const extension = defineSearchQuery({
  search: 'ProseKit',
  replace: 'Editor',
  caseSensitive: false,
  regexp: false,
  wholeWord: false,
})

API Reference

The search string or regular expression pattern.
replace
string
The replacement text for replace operations.
caseSensitive
boolean
default:"false"
Whether the search is case-sensitive.
literal
boolean
default:"false"
When true, disables automatic conversion of \n, \r, and \t in the search string.
regexp
boolean
default:"false"
When true, interprets the search string as a regular expression.
wholeWord
boolean
default:"false"
Enable whole-word matching (only match complete words).

Commands

The search extension provides several commands for navigation and replacement:
interface SearchCommands {
  findNext: () => Command
  findPrev: () => Command
  findNextNoWrap: () => Command
  findPrevNoWrap: () => Command
  replaceNext: () => Command
  replaceNextNoWrap: () => Command
  replaceCurrent: () => Command
  replaceAll: () => Command
}

Command Descriptions

findNext
Command
Jump to the next match, wrapping to the beginning if at the end.
findPrev
Command
Jump to the previous match, wrapping to the end if at the beginning.
findNextNoWrap
Command
Jump to the next match without wrapping.
findPrevNoWrap
Command
Jump to the previous match without wrapping.
replaceNext
Command
Replace the current match and jump to the next one (with wrapping).
replaceNextNoWrap
Command
Replace the current match and jump to the next one (without wrapping).
replaceCurrent
Command
Replace only the current match without moving to the next one.
replaceAll
Command
Replace all matches in the document at once.

Complete Example

import { createEditor } from '@prosekit/core'
import { defineBasicExtension } from '@prosekit/extensions/basic'
import { defineSearchQuery, defineSearchCommands } from '@prosekit/extensions/search'
import { union } from '@prosekit/core'

const editor = createEditor({
  extension: union([
    defineBasicExtension(),
    defineSearchQuery({
      search: 'example',
      replace: 'sample',
    }),
    defineSearchCommands(),
  ]),
})

// Navigate through matches
editor.commands.findNext()
editor.commands.findPrev()

// Replace operations
editor.commands.replaceCurrent()
editor.commands.replaceAll()

React Search UI

import { useEditor } from '@prosekit/react'
import { useState } from 'react'

function SearchPanel() {
  const editor = useEditor()
  const [searchText, setSearchText] = useState('')
  const [replaceText, setReplaceText] = useState('')
  const [caseSensitive, setCaseSensitive] = useState(false)
  const [useRegex, setUseRegex] = useState(false)
  
  return (
    <div className="search-panel">
      <div className="search-inputs">
        <input
          type="text"
          placeholder="Search..."
          value={searchText}
          onChange={(e) => setSearchText(e.target.value)}
        />
        <input
          type="text"
          placeholder="Replace..."
          value={replaceText}
          onChange={(e) => setReplaceText(e.target.value)}
        />
      </div>
      
      <div className="search-options">
        <label>
          <input
            type="checkbox"
            checked={caseSensitive}
            onChange={(e) => setCaseSensitive(e.target.checked)}
          />
          Case sensitive
        </label>
        <label>
          <input
            type="checkbox"
            checked={useRegex}
            onChange={(e) => setUseRegex(e.target.checked)}
          />
          Use regex
        </label>
      </div>
      
      <div className="search-actions">
        <button onClick={() => editor.commands.findPrev()}>
          Previous
        </button>
        <button onClick={() => editor.commands.findNext()}>
          Next
        </button>
        <button onClick={() => editor.commands.replaceCurrent()}>
          Replace
        </button>
        <button onClick={() => editor.commands.replaceAll()}>
          Replace All
        </button>
      </div>
    </div>
  )
}

Styling Search Matches

The extension adds CSS classes to search matches:
/* All search matches */
.ProseMirror-search-match {
  background-color: #fef3c7;
  border-radius: 2px;
}

/* The currently active match */
.ProseMirror-active-search-match {
  background-color: #fbbf24;
  outline: 2px solid #f59e0b;
}

/* Dark mode */
.dark .ProseMirror-search-match {
  background-color: #854d0e;
}

.dark .ProseMirror-active-search-match {
  background-color: #a16207;
  outline-color: #ca8a04;
}

Advanced Examples

import { defineSearchQuery } from '@prosekit/extensions/search'

const extension = defineSearchQuery({
  search: 'https?://[^\\s]+',
  regexp: true,
})

// Find all URLs in the document
editor.commands.findNext()
import { defineSearchQuery } from '@prosekit/extensions/search'

const extension = defineSearchQuery({
  search: 'cat',
  wholeWord: true,
})

// Matches "cat" but not "category" or "concatenate"
import { defineSearchQuery } from '@prosekit/extensions/search'

const extension = defineSearchQuery({
  search: 'ProseKit',
  caseSensitive: true,
})

// Matches "ProseKit" but not "prosekit" or "PROSEKIT"
Update the search query dynamically:
import { createEditor } from '@prosekit/core'
import { SearchQuery } from 'prosemirror-search'

function updateSearch(editor: Editor, searchText: string) {
  // Get the search plugin
  const searchPlugin = editor.view.state.plugins.find(
    (p) => p.key === 'prosemirror-search'
  )
  
  if (searchPlugin) {
    const query = new SearchQuery({
      search: searchText,
      caseSensitive: false,
      regexp: false,
    })
    
    // Update the search
    editor.view.dispatch(
      editor.view.state.tr.setMeta(searchPlugin, { query })
    )
  }
}

Use Cases

Find & Replace

Enable users to find and replace text throughout their documents.

Content Review

Help users locate specific terms or phrases during content review.

Refactoring

Support bulk renaming and refactoring in code editors.

Translation

Find and replace terms during localization and translation workflows.

Keyboard Shortcuts

Consider adding keyboard shortcuts for common search operations:
import { defineKeymap } from '@prosekit/core'

const searchKeymap = defineKeymap({
  'Mod-f': () => {
    // Open search panel
    return true
  },
  'Mod-g': findNext,
  'Mod-Shift-g': findPrev,
  'Mod-h': () => {
    // Open replace panel
    return true
  },
  'Escape': () => {
    // Close search panel
    return true
  },
})

Tips

The search automatically scrolls the active match into view, ensuring users can always see the current result.
Use findNextNoWrap and findPrevNoWrap when you want to prevent wrapping to the beginning/end of the document.
Be careful with replaceAll on large documents with many matches. Consider showing a confirmation dialog first.
Search decorations are updated automatically as the document changes. Matches remain highlighted even as users edit.