Configuration

In the typical style of Emacs, Projectile is extremely configurable. Pretty much every aspect of its behaviour can be tweaked or extended.

In this section we’ll go over some of the most common things you might want to fine-tune to make Projectile fit your workflow better.

A couple of larger configuration topics have their own pages: indexing, caching & performance and per-project sessions.

Using Projectile commands outside of project directories

Normally, you’d be using Projectile’s commands from within some project directory. If, however, you invoke a command outside of a project, by default you’ll be prompted for a project to switch to. That behavior is controlled by projectile-require-project-root. You can make Projectile simply raise an error outside of Project folders like this:

(setq projectile-require-project-root t)

If you want Projectile to be usable in every directory (even without the presence of project file):

(setq projectile-require-project-root nil)

With this setting if you invoke Projectile outside of a project, the current directory will be considered by Projectile the project root.

This might not be a great idea if you start Projectile in your home folder for instance. :-)

Switching projects

By default, projectile does not include the current project in the list when switching projects. If you want to include the current project, customize variable projectile-current-project-on-switch.

When running projectile-switch-project (s-p p) and projectile-switch-open-project (s-p q) Projectile invokes the command specified in projectile-switch-project-action (by default it is projectile-find-file).

Invoking the command with a prefix argument (C-u s-p p or C-u s-p q) will trigger the Projectile dispatch menu (projectile-dispatch), which gives you quick access to most common commands you might want to invoke on a project.

Depending on your personal workflow and habits, you may prefer to alter the value of projectile-switch-project-action:

projectile-find-file

This is the default.

With this setting, once you have selected your project via Projectile’s completion system (see below), you will remain in the completion system to select a file to visit. projectile-find-file is capable of retrieving files in all sub-projects under the project root, such as Git submodules. Currently, only Git is supported. Support for other VCS will be added in the future.

Dispatch menu on project switch

If you often need to invoke a different action right after switching to a project, press C-u s-p p (a prefix argument to any switch-project command). After you pick a project, Projectile opens the projectile-dispatch menu so you can choose what to do in it.

This requires the optional transient dependency. Without it the prefix argument is ignored and the usual projectile-switch-project-action runs.

projectile-find-file-in-known-projects

Similar to projectile-find-file but lists all files in all known projects. Since the total number of files could be huge, it is beneficial to enable caching for subsequent usages.

projectile-find-file-dwim

If point is on a filepath, Projectile first tries to search for that file in project:

  • If it finds just a file, it switches to that file instantly. This works even if the filename is incomplete, but there’s only a single file in the current project that matches the filename at point. For example, if there’s only a single file named "projectile/projectile.el" but the current filename is "projectile/proj" (incomplete), projectile-find-file still switches to "projectile/projectile.el" immediately because this is the only filename that matches.

  • If it finds a list of files, the list is displayed for selecting. A list of files is displayed when a filename appears more than one in the project or the filename at point is a prefix of more than two files in a project. For example, if `projectile-find-file' is executed on a filepath like "projectile/", it lists the content of that directory. If it is executed on a partial filename like "projectile/a", a list of files with character 'a' in that directory is presented.

  • If it finds nothing, display a list of all files in project for selecting.

projectile-dired

(setq projectile-switch-project-action #'projectile-dired)

With this setting, once you have selected your project, the top-level directory of the project is immediately opened for you in a dired buffer.

projectile-find-dir

(setq projectile-switch-project-action #'projectile-find-dir)

With this setting, once you have selected your project, you will remain in Projectile’s completion system to select a sub-directory of your project, and then that sub-directory is opened for you in a dired buffer. If you use this setting, then you will probably also want to set

(setq projectile-find-dir-includes-top-level t)

in order to allow for the occasions where you want to select the top-level directory.

Known projects

Projectile stores the list of known projects in a file on disk. You can customize its location:

(setq projectile-known-projects-file "~/.emacs.d/projectile-bookmarks.eld")

Ignoring projects

You can prevent certain projects from being added to the known projects list:

;; A list of projects to ignore
(setq projectile-ignored-projects '("/tmp/" "~/.emacs.d/"))

For more flexible filtering, set projectile-ignored-project-function to a predicate that receives a project root and returns non-nil if the project should be ignored:

;; Ignore all remote (TRAMP) projects
(setq projectile-ignored-project-function #'file-remote-p)

Automatic tracking

By default, Projectile automatically adds projects to the known projects list whenever you visit a file in a project. You can disable this:

(setq projectile-track-known-projects-automatically nil)

Completion options

Projectile reads with Emacs’s built-in completing-read, so it works out of the box with whatever minibuffer UI you use - Vertico, Consult + Marginalia, fido-mode/icomplete, and so on. Its candidates are tagged with the project-file completion category, which Marginalia and Embark use to enhance how they’re presented.

;; the default; nothing to configure for the common case
(setq projectile-completion-system 'default)
Earlier versions had dedicated ido, ivy and helm completion systems; these were removed in Projectile 3.0, and any of the old projectile-completion-system values now simply behaves like default. Those frameworks are used through completing-read these days, or through their own integration packages, helm-projectile and counsel-projectile. ido users in particular should read the upgrade guide, which covers ido-completing-read+.

Custom completion function

You can also set projectile-completion-system to a function that accepts a prompt and a list of choices:

(setq projectile-completion-system #'my-custom-completion-fn)
(setq projectile-completion-system
      (lambda (prompt choices)
        ;; ...
        ))

An example of a custom completion function is this one, which only shows the file name (not including path) and, if the selected file is not unique, follows up with a completion over names relative to the project root.

Project-specific compilation buffers

This affects all commands built on top of projectile—​run-project-cmd like:

  • projectile-configure-project

  • projectile-run-project

  • projectile-test-project

  • projectile-install-project

  • projectile-package-project

Normally, the buffers created by those commands would be shared (overwritten) between projects, but it’s also possible to make the compilation buffer names project-specific. This requires that the user set:

(setq projectile-per-project-compilation-buffer t)

Both of these degrade properly when not inside a project.

Limit the number of project file buffers

Projectile can be configured to keep a maximum number of file buffers of a project that are opened at one point. The custom variable projectile-max-file-buffer-count can be set to an integer that will be the buffer count cap. If this limit is reached, by opening a new file, Projectile will close the least recent buffer of the current project. If the variable is nil, there will be no cap on the buffer count.

(setq projectile-max-file-buffer-count 10)

Note that special project buffers (e.g. compilation, dired, etc) are not affected by this setting.

Mode line indicator

By default the minor mode indicator of Projectile appears in the form " Projectile[ProjectName:ProjectType]". This is configurable via several custom variables:

  • projectile-mode-line-prefix (by default " Projectile") controls the static part of the mode-line

  • projectile-dynamic-mode-line (by default t) controls whether to display the project name & type part of the mode-line

  • projectile-mode-line-function (by default projectile-default-mode-line) controls the actual function to be invoked to generate the mode-line. If you’d like to show different info you should supply a custom function to replace the default, for example (setq projectile-mode-line-function '(lambda () (format " Proj[%s]" (projectile-project-name))))

The project name & type will not appear when editing remote files (via TRAMP), as recalculating the project name is a fairly slow operation there and would slow down a bit opening the files. They will also not appear for non-file buffers, as they get updated via find-file-hook.

Searching

projectile-search (s-p s s) searches the project using a pluggable backend. Projectile ships three backends - grep, ripgrep and ag - and you pick which one projectile-search uses via projectile-search-backend:

;; `auto' (the default) picks the first available backend, favouring ripgrep,
;; then grep (which is always available).  You can also name a specific backend
;; or use `prompt' to be asked each time.
(setq projectile-search-backend 'ripgrep)

The dedicated commands projectile-grep (s-p s g), projectile-ripgrep (s-p s r) and projectile-ag (s-p s a) still exist; they’re just projectile-search with a forced backend.

For the grep backend, in Git projects you can use vc-git-grep instead of rgrep, which tends to be faster as it respects .gitignore:

(setq projectile-use-git-grep t)

Adding your own search backend

Register a backend with projectile-register-search-backend. It takes a name, a :description, an optional :available predicate, and a :search function called with the search term and a flag indicating whether it’s a regexp. For example, to add deadgrep:

(projectile-register-search-backend 'deadgrep
  :description "deadgrep"
  :available (lambda () (require 'deadgrep nil t))
  :search (lambda (term _regexp)
            (deadgrep term (projectile-acquire-root))))

(setq projectile-search-backend 'deadgrep)

Shells, REPLs and terminals

projectile-run (s-p x r) opens a shell, REPL or terminal in the project root using a pluggable backend, the same way projectile-search works. Projectile ships shell, eshell, ielm, term, vterm, eat and ghostel backends; projectile-shell-backend selects which one is used (default eshell):

(setq projectile-shell-backend 'vterm)

The dedicated commands (projectile-run-eshell, projectile-run-vterm, …​, plus the -other-window variants) are kept; they’re just projectile-run with a forced backend.

Register your own terminal with projectile-register-shell-backend - it takes a name, a :description, an optional :available predicate, and a :run function called with a new-process flag (the prefix argument) and an other-window flag:

(projectile-register-shell-backend 'mistty
  :description "mistty"
  :available (lambda () (require 'mistty nil t))
  :run (lambda (_new-process _other-window)
         (let ((default-directory (projectile-acquire-root)))
           (mistty))))

The generic registry machinery behind both projectile-search and projectile-run (projectile-register-backend) is intended to be reused for other pluggable command families in the future.

Project name

The project name displayed in the mode line and used in buffer names is determined by projectile-project-name-function. The default implementation uses the project root directory name, but you can provide your own:

(setq projectile-project-name-function
      (lambda (project-root)
        (file-name-nondirectory
         (directory-file-name project-root))))
If the variable projectile-project-name is set (e.g. via .dir-locals.el), it takes precedence over the function.

Hooks

Projectile provides several hooks for integrating with your workflow:

;; Run after a file is opened via projectile-find-file
(add-hook 'projectile-find-file-hook #'my-find-file-setup)

;; Run after a directory is opened via projectile-find-dir
(add-hook 'projectile-find-dir-hook #'my-find-dir-setup)

;; Run right before switching projects
(add-hook 'projectile-before-switch-project-hook #'my-before-switch-setup)

If you want to react to any change of the current project, not just explicit invocations of projectile-switch-project, use projectile-project-changed-functions (added in Projectile 3.1). Its functions are also run when you enter another project implicitly, e.g. by visiting one of its files or directories. Each function is called with the new project root and the previous one (nil when there was none):

(add-to-list 'projectile-project-changed-functions
             (lambda (new previous)
               (message "Moved from project %s to %s" previous new)))
Moving to a buffer outside any project doesn’t count as a change; the functions run again only when another project is entered.

Miscellaneous

Verbose messages

By default Projectile echoes informational messages (not just errors). To suppress them:

(setq projectile-verbose nil)

Projectile adds a menu to the Emacs menu bar by default. To disable it:

(setq projectile-show-menu nil)

Project-type-specific configuration

CMake

Projectile supports CMake presets. Preset support is disabled by default, but can be enabled by setting projectile-enable-cmake-presets to non-nil. With preset-support enabled Projectile will parse the preset files and present the command-specific presets when executing a lifecycle command. In addition a no preset option is included for entering the command manually.

Preset support requires a CMake version that supports preset and for json-parse-buffer to be available.