Hung-Yi's LogoHung-Yi’s Journal

How to Do a Project-wide Search & Replace in Doom Emacs

This is a guide on how to do a project-wide text search and replace operation using Doom Emacs and all its preinstalled packages and keybindings.

Earlier this week I went looking for “how to search and replace in a whole project” in Emacs. I knew had Doom Emacs set up with evil turned on by default, and I knew to use SPC s p to launch a project text search. What was next…?

Today, I’m documenting the process so that I won’t forget again. Hopefully it’ll help someone out there too.


  • for ivy module users
    SPC s p foo C-c C-e :%s/foo/bar/g RET Z Z
  • for vertico module users
    SPC s p foo C-; E C-c C-p :%s/foo/bar/g RET Z Z

Entering those keys will replace “foo” with “bar” in your whole project. Magic!

Not sure if you’re using ivy or vertico? If you set up Doom Emacs after [2021-07-29 Thu], you’re probably using vertico. Otherwise, probably ivy. Check your init.el file to see which is active1

The Long Explanation

  1. Make sure you’re in the right project and all your open buffers/files are saved. If you’re making a large change, maybe a git commit would give you a little extra insurance.
  2. SPC s p to launch the project search. This is what you would normally use to search strings or regular expressions2 within your project. I’ve set ivy-more-chars-alist to not return any results until at least 3 characters have been entered3, otherwise counsel can be a little overzealous in returning results.
  3. Type in a search string or regular expression, foo in this example, to get some results. This doesn’t have to be exactly what you want to replace; it just needs to be specific enough to get the right results into view.
  4. This step depends on which completion engine you’ve set up for Doom.
    1. for ivy users1
      C-c C-e to transform the search results into a wgrep or writable grep buffer. This calls +ivy/woccur, which is equivalent to pressing C-c C-o C-c C-p (i.e. ivy-occur for turning ivy results into a buffer and wgrep-change-to-wgrep-mode to make it writable).
    2. for vertico users1
      C-; to run embark-act. This Embark command is like a context menu for what you’re currently looking at.

      E to run embark-export, which exports what you’re looking at into a new buffer. Embark is smart enough to know that you’re looking at grep search results, so it will export to a new buffer in grep-mode

      C-c C-p to run wgrep-change-to-wgrep-mode to make the search results writable.

  5. Use your preferred method of search and replace in a single file. Doom relies heavily on evil’s vim emulation, so the smoothest option for me was to type :%s/foo/bar/g to preview the changes and hitting RET to apply.

    You can also edit the buffer manually using any technique you want. M-% or C-M-% to do a regular Emacs query replace? Sure. C-x SPC or C-v to do a rectangle/visual block edit? Go for it. g z z for evil-mc multiple cursors? No worries—just be careful of the readonly region of filenames on the left.

    At this stage nothing has been written to the files yet, so you can still undo easily. Go through all the lines in the search results to check if the changes are as expected.

    You can also SPC : wgrep or M-x wgrep to look at the other advanced wgrep actions.

  6. Hit Z Z to write all the changes to their respective files, or Z Q to abort the search and replace.



This post was originally written back when ivy was still the default completion engine used by Doom Emacs. However, since this commit on [2021-07-29 Thu], new users of Doom will be using the vertico completion stack. Existing users upgrading to the current version of Doom will keep using Ivy.

How can you tell if you’re using Ivy or vertico? Have a look at your init.el file in your Doom config to check if ivy or vertico are present and uncommented. Whichever one is uncommented is the one you’re using.


Just a reminder that Emacs has its own idiosyncratic version of regular expressions. It’s weird, I know.


I have this in my config.el to set the minimum characters to 3 before a search is fired. You can customize the threshold individually for each of the commands.

(after! ivy
  (setq ivy-more-chars-alist '((counsel-grep . 3)
                               (counsel-rg . 3)
                               (counsel-search . 3)
                               (t . 3))))