= Projectile versus project.el

NOTE: As both projects are moving targets this page might not reflect the differences between them accurately.

Projectile was created at a time when Emacs didn't feature built-in project
navigation functionality. Eventually this changed in Emacs 25 with the introduction of `project.el` and a lot of people have been asking what are the advantages of using
Projectile over the built-in library. This section of the documentation will try to answer this question.

When `project.el` was originally introduced it's feature-set was quite spartan, but it has added some new features in every Emacs release and circa 2026 it should cover the needs of most casual users. I'm guessing that Projectile inspired many features in `project.el`, and Projectile itself was inspired by tools like IntelliJ IDEA.

== TL;DR

If the functionality in `project.el` is good enough for you then you should probably use `project.el`.

== At a glance

NOTE: As of early 2026. (Projectile 2.10 and `project.el` in Emacs 31)

|===
| | Projectile | project.el

| Created in
| 2011
| 2014 footnote:[It was introduced in Emacs 25.1.]

| Supported Emacs versions
| 28.1+
| 26+ footnote:[Note that the versions bundled with older Emacsen will miss some of its modern features. `project.el` is distributed as a package as well, so you can still get some of the newer functionality on older Emacsen.]

| Built-in
| no
| yes

| Package Availability
| MELPA, MELPA Stable, NonGNU ELPA
| GNU ELPA

| Indexing Strategies
| 3 (`native`, `hybrid`, `alien`)
| 1 (similar to Projectile's alien strategy)

| Native Project Config
| Yes (`.projectile`)
| No (uses `.dir-locals.el`)

| Project Cache
| Yes (persisted to disk)
| In-memory only

| Backend Protocol
| Project-type alist
| `cl-defgeneric` / `cl-defmethod`

| Built-in Project Types
| 60+
| n/a

| Number of Configuration Options
| ~95
| ~25

| Number of Commands
| ~90
| ~35

| Minor Mode
| Yes
| No

| Global Keybindings
| Require user setup
| Out-of-the-box (`C-x p`)

| Integration with `xref`
| Yes
| Yes

| Feature-set
| Extensive
| Essential

| Extensions
| ~20
| ~10

| Contribution Process
| Light-weight
| Somewhat complicated (standard for Emacs)
|===

== Notable differences

=== Minor mode vs global keymap

Projectile makes heavy use of `projectile-mode`, which provides some additional features (e.g. project status in the modeline).
You can use Projectile without it, but I guess few people do so. Most of Projectile's functionality will work fine even if the mode is not active.

`project.el` on the other hand, just adds its own keymap under the global keymap (using the prefix `C-x p`). You can do the same for Projectile,
of course, if you want to.

=== Project indexing strategies

Projectile has multiple project indexing strategies to cover a wide variety of use cases. `project.el` has only one, which is more or less the same
as Projectile's `alien` strategy. Admittedly, that's probably the most commonly used strategy.

=== `.projectile`

Projectile has its own project marker/configuration file. It's a remnant of the early days of the project where I wanted to build a tool that didn't
rely on the third-party applications and its significance today is not that big. (it will be completely ignored unless you're using `native` or `hybrid` indexing)

=== Backend protocol

`project.el` defines a small protocol via `cl-defgeneric` (`project-root`, `project-files`, `project-buffers`, etc.) and ships two backends: a transient one for ad-hoc directories and a VC-aware one. New backends are added with `cl-defmethod`.

Projectile detects projects via root markers and expresses per-language behavior through *project types* (`projectile-register-project-type`). More opinionated, more out of the box, less polymorphic.

== What `project.el` does that Projectile doesn't (yet)

A few `project.el` features have no real equivalent in Projectile:

* `project-find-matching-buffer` — jump to the equivalent buffer in another known project, matched by relative path. Handy for forks and parallel-layout microservices.
* `project-file-history-behavior` set to `relativize` — when switching projects, file-name history is rewritten as relative paths, so muscle memory carries across projects.
* `project-other-window-command` / `-other-frame-command` / `-other-tab-command` — prefix commands that redirect the next project command's output. Projectile takes the brute-force route instead: a `-other-window` and `-other-frame` variant for almost every command.
* `project-prompter` and `project-read-file-name-function` — single-function knobs for the project / file-name prompts. Cleaner extension point than `projectile-completion-system`.
* `project-prune-zombie-projects` (Emacs 31) — finer-grained pruning triggers for zombie projects. Projectile only has a single boolean (`projectile-auto-cleanup-known-projects`).
* `project-vc-cache-timeout` as an alist of `(predicate . timeout)` pairs, so e.g. remote and local can have different TTLs.
* `project-vc-merge-submodules` — explicit Git submodule strategy. Projectile has none.
* Sparse-index support on Emacs 31 with Git ≥ 2.35 (`git ls-files --sparse`).

We're at parity (or close) on a few others:

* `project-customize-dirlocals` vs `projectile-edit-dir-locals` (we just open the file).
* `project-vc-name` and `project-vc-extra-root-markers` are covered by `projectile-project-name` (via dir-locals) and the project-type system.
* `project-list-exclude` is covered by `projectile-ignored-projects` and `projectile-ignored-project-function` (the latter even accepts a predicate, e.g. `file-remote-p`).

== Concept and command concordance

Side-by-side mapping of the most common entry points. Useful both when picking between the two and when switching from one to the other. Empty cells mean there's no direct equivalent (which doesn't necessarily mean the functionality can't be had — just that you'd need to wire something up yourself).

=== Project root and detection

|===
| Concept | Projectile | project.el

| Project root of `default-directory`
| `projectile-project-root`
| `project-root` (via `project-current`)

| Project name
| `projectile-project-name`
| `project-name`

| Detection hook
| `projectile-project-root-functions`
| `project-find-functions`

| Extra project markers
| `projectile-project-root-files-bottom-up` (and friends)
| `project-vc-extra-root-markers`

| Native config file
| `.projectile`
| —
|===

=== File and directory navigation

|===
| Concept | Projectile | project.el

| Find file in project
| `projectile-find-file`
| `project-find-file`

| Find file in known projects
| `projectile-find-file-in-known-projects`
| —

| Find directory
| `projectile-find-dir`
| `project-find-dir`

| Open dired in root
| `projectile-dired`
| `project-dired`

| Toggle test ↔ implementation
| `projectile-toggle-between-implementation-and-test`
| —

| Find related file
| `projectile-find-other-file`, `projectile-find-related-file`
| —

| "Other window/frame" variants
| `…-other-window` / `…-other-frame` per command
| `project-other-window-command` / `-other-frame-command` / `-other-tab-command` (prefix style)
|===

=== Buffer management

|===
| Concept | Projectile | project.el

| Switch to project buffer
| `projectile-switch-to-buffer`
| `project-switch-to-buffer`

| List project buffers
| `projectile-display-buffer` / `projectile-ibuffer`
| `project-list-buffers` (via `project-buffers-viewer`)

| Kill project buffers
| `projectile-kill-buffers`
| `project-kill-buffers`

| Save project buffers
| `projectile-save-project-buffers`
| `project-save-some-buffers`

| Find equivalent buffer in another project
| —
| `project-find-matching-buffer`
|===

=== Search and replace

|===
| Concept | Projectile | project.el

| Grep
| `projectile-grep`
| —

| Ripgrep
| `projectile-ripgrep`
| —

| Ag
| `projectile-ag`
| —

| Find regex via xref
| —
| `project-find-regexp`

| Multi-occur
| `projectile-multi-occur`
| —

| Query-replace
| `projectile-replace`, `projectile-replace-regexp`
| `project-query-replace-regexp`

| Find references
| `projectile-find-references`
| (use `xref-find-references`)
|===

=== Build, test, run, VC

|===
| Concept | Projectile | project.el

| Compile
| `projectile-compile-project`
| `project-compile`

| Recompile
| (re-runs last compile per project)
| `project-recompile`

| Test
| `projectile-test-project`
| —

| Run
| `projectile-run-project`
| —

| Configure
| `projectile-configure-project`
| —

| Install
| `projectile-install-project`
| —

| Package
| `projectile-package-project`
| —

| VC dir
| `projectile-vc`
| `project-vc-dir`
|===

=== Shell, terminal, REPL

|===
| Concept | Projectile | project.el

| Shell command in root
| `projectile-run-shell-command-in-root`
| `project-shell-command`

| Async shell command in root
| `projectile-run-async-shell-command-in-root`
| `project-async-shell-command`

| `M-x shell`
| `projectile-run-shell`
| `project-shell`

| `M-x eshell`
| `projectile-run-eshell`
| `project-eshell`

| `M-x term`
| `projectile-run-term`
| —

| Vterm
| `projectile-run-vterm`
| —

| Eat
| `projectile-run-eat`
| —

| Ghostel
| `projectile-run-ghostel`
| —

| IELM
| `projectile-run-ielm`
| —

| GDB
| `projectile-run-gdb`
| —
|===

=== Project switching and known projects

|===
| Concept | Projectile | project.el

| Switch project
| `projectile-switch-project`
| `project-switch-project`

| Switch to open project
| `projectile-switch-open-project`
| —

| Forget project
| `projectile-remove-known-project`
| `project-forget-project`

| Forget current
| `projectile-remove-current-project-from-known-projects`
| —

| Forget projects under dir
| `projectile-forget-projects-under`
| `project-forget-projects-under`

| Remember project
| `projectile-add-known-project`
| `project-remember-project`

| Discover projects in path
| `projectile-discover-projects-in-search-path`
| —

| Clean up zombies (manual)
| `projectile-cleanup-known-projects` (aliased as `projectile-forget-zombie-projects`)
| `project-forget-zombie-projects`

| Auto-clean zombies
| `projectile-auto-cleanup-known-projects` (boolean)
| `project-prune-zombie-projects` (granular triggers)

| Known-projects file
| `projectile-known-projects-file`
| `project-list-file`

| Block from being remembered
| `projectile-ignored-projects`, `projectile-ignored-project-function`
| `project-list-exclude`
|===

=== Configuration and extension

|===
| Concept | Projectile | project.el

| Custom completion UI
| `projectile-completion-system` (enum)
| `project-prompter` (function)

| Custom file-name reader
| —
| `project-read-file-name-function`

| Persistent file cache
| `projectile-cache-file`
| —

| Ignore patterns
| `projectile-globally-ignored-files`, `…-directories`, `…-file-suffixes`, `.projectile`
| `project-vc-ignores`, plus `.gitignore`/`.hgignore`

| Per-project name override
| `projectile-project-name` (via dir-locals)
| `project-vc-name` (via dir-locals)

| Edit dir-locals
| `projectile-edit-dir-locals`
| `project-customize-dirlocals`

| Backend extension
| `projectile-register-project-type` (project types)
| `cl-defmethod` on the project protocol

| Switch-project hook
| `projectile-before-switch-project-hook`, `projectile-after-switch-project-hook`
| —
|===

== Projectile's pros

* Projectile targets Emacs 28.1+
* Projectile has different project indexing strategies, which offer you a lot of flexibility in different situations
* Projectile supports a lot of project types out-of-the-box (e.g. `ruby`, `Rails`, `cabal` and `dune`), with per-type hooks for compile/test/run/install/package/configure
* Projectile has a lot more features, although one can argue that some of them are rarely needed
  ** Projectile's dispatch menu (`projectile-dispatch`, a `transient` interface) is pretty cool for driving a project!
  ** Test/implementation toggling, a related-files framework, persistent on-disk caching, integrations with most Emacs shells out of the box
* It's easier to contribute to Projectile
  ** Projectile is hosted in GitHub
  ** It accepts pull requests
  ** You don't need to sign a contributor agreement
* Projectile has more extensive documentation
  ** You can compare it with https://www.gnu.org/software/emacs/manual/html_node/emacs/Projects.html[project.el's documentation] and decide for yourself

== Projectile's cons

* Third-party dependency, developed outside of Emacs. This is both a pro and con depending on one's perspective, but I know that many people prefer built-in packages, so I've put it under "cons".
  ** Built-in packages in theory should be maintained better (or at least for longer), as they have the Emacs team behind them.
  ** While Projectile has a rich ecosystem of extensions, over a long enough period of time likely `project.el` will take the lead.
* Due to its larger size, one can argue that Projectile is more complex than `project.el`
  ** Admittedly I would have done some things differently if I were starting Projectile today, but I don't think Projectile's core complexity is high.
* `project.el` keeps gaining features each release, and a few of them have no Projectile equivalent yet (see the section above).
