Choosing an Emacs Package Manager: package.el, straight.el, or Elpaca
A decision guide for Emacs package management: compare package.el+use-package, straight.el, and Elpaca on reproducibility, speed, and patching, with a minimal working config and validation steps.
14 May 2026, 05:26 UTC

The decision you are actually making
Every Emacs configuration eventually needs third-party packages, and the way you install them determines how reproducible, fast, and maintainable your setup is. The three realistic options today are the built-in package.el (usually paired with use-package), straight.el, and Elpaca. They are not interchangeable: they differ in where packages come from, whether you can pin exact versions, and how much waiting you do at startup and update time.
One clarification up front: use-package is a configuration macro, not a package manager. It declares what you want and how to configure it; something else — package.el, straight.el, or Elpaca — does the actual installing. Choosing a use-package syntax and choosing an installer are two separate decisions, and all three installers support use-package-style declarations.
Constraints that drive the choice
Before comparing, be honest about which of these matter to you:
- Reproducibility. Do you need the same package versions on your laptop, desktop, and CI machine? MELPA builds rolling snapshots from upstream development branches, so "install Magit" can mean different code on different days.
- Startup and update speed. Installing from tarballs is fast; cloning and byte-compiling from Git is slower, and doing it synchronously blocks Emacs.
- Patching. Do you ever need to carry a local fix against an upstream package before the maintainer merges it?
- Setup cost and community examples. Most published configs assume package.el; deviating means translating snippets yourself.
The three options compared
| package.el + use-package | straight.el | Elpaca | |
|---|---|---|---|
| Availability | Built into Emacs; use-package bundled since Emacs 29 | External, bootstrapped in init | External, bootstrapped in init |
| Package source | Byte-compiled tarballs from ELPA/MELPA | Git clones of upstream repos | Git clones of upstream repos |
| Version pinning | Weak; MELPA Stable helps but lags | Strong; exact revisions in a lockfile | Lockfile support for reproducible installs |
| Install/update behavior | Fast, synchronous | Slower first install, synchronous | Asynchronous; non-blocking installs and builds |
| Local patching | Awkward; you shadow files manually | First-class; edit the clone, keep the revision | Similar git-native workflow |
| Maturity | Decades old, ubiquitous | Established, active | Newer; smaller docs and community |
Trade-offs in practice
Choose package.el + use-package if you want the lowest setup cost and maximum compatibility with configurations you find online. The catch is reproducibility: two machines installing "the same" config a week apart can get different package code. You can mitigate this with MELPA Stable or by committing ~/.emacs.d/elpa, but both are partial fixes. Rollback after a bad upgrade is manual.
Choose straight.el if reproducibility is the priority. It clones each package's Git repository, builds it, and records the exact commit in a lockfile you commit alongside your init. Restoring a machine means replaying the lockfile. You can also edit a package's source directly in its clone and keep that patch across updates. The costs are a noticeably slower first install (cloning and compiling everything), synchronous operations that block Emacs during updates, and a steeper learning curve. Its use-package integration must be enabled explicitly.
Choose Elpaca if you want straight.el-style git-based, lockfile-backed installs but find the blocking behavior painful. Elpaca performs installs and builds asynchronously, so startup and updates don't freeze your editor. The trade-off is youth: the documentation and community knowledge base are smaller, and the project evolves faster, so check its README for current install snippets and Emacs version requirements before committing a long-lived or team config to it.
One hazard regardless of choice: don't let two managers install the same library. Mixing package.el and straight.el copies of a package produces duplicate or shadowed load-path entries and confusing bugs. Pick one installer per Emacs instance.
A concrete minimal setup: package.el path
If you land on the built-in option — the right default for most single-machine setups — a minimal init.el looks like this:
(require 'package)
(add-to-list 'package-archives
'("melpa" . "https://melpa.org/packages/") t)
(package-initialize)
;; use-package is built in on Emacs 29+; install it explicitly on older versions
(use-package magit
:ensure t
:bind ("C-x g" . magit-status))
(use-package company
:ensure t
:hook (prog-mode . company-mode))
The :ensure t keyword tells use-package to ask package.el to install the package if it is missing. Commit this init.el to version control — with package.el, the declarative config is your reproducibility artifact, imperfect as it is. Placeholders to adjust: the package names, the key binding, and the archive list (add melpa-stable if you prefer pinned-ish releases over snapshots).
Validating the result
Whatever manager you choose, verify rather than assume:
- Restart with
emacs --debug-initfrom a terminal. This surfaces config errors with a backtrace instead of a half-loaded editor. Run it as your normal user; no special permissions are needed. - Confirm a package is actually installed and loaded:
M-x describe-package RET magit RET, orM-x locate-library RET magit RETto see exactly which file is on the load path — useful for catching shadowed duplicates. - Measure startup cost with
M-x emacs-init-timebefore and after adding packages, so you know what your declarations cost. - Test reproducibility: launch Emacs with a temporary clean
HOMEcontaining only your init file (for example,HOME=/tmp/emacs-test emacson a Unix-like shell) and confirm the same packages install and load. This is the closest practical simulation of a fresh machine.
Limitations and a note on currency
This comparison reflects well-established behavior as of Emacs 29 and later, but straight.el and Elpaca in particular are moving targets — feature sets, bootstrap snippets, and minimum Emacs versions change. Before adopting either for a team or a config you expect to keep for years, read its current README. And remember that MELPA's rolling snapshot model is a property of the archive, not the manager: even with a lockfile-based tool, updates pull new upstream code when you ask them to. The lockfile protects you from drift you didn't request, not from updates you did.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.