Projectile versus project.el

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.

TLDR;

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

At a glance

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

Created in

2011

2014 [1]

Supported Emacs versions

28.1+

26+ [2]

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

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).


1. It was introduced in Emacs 25.1.
2. 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.