Emacs package management with straight.el and use-package

Emacs includes a package manager named package.el, which installs packages from the official Emacs Lisp Package Archive, named GNU ELPA. GNU ELPA hosts a selection of packages, but most are available on MELPA, which is an unofficial package archive that implements the ELPA specification. To use MELPA, it has to be installed by adding it to the list of package.el package archives.

The built-in package manager installs packages through the package-install function. For example, to install the “evil-commentary” package from MELPA, call package-install inside Emacs:

M-x package-install <RET> evil-commentary <RET>

Straight.el

Straight.el is an alternative package manager that installs packages through Git checkouts instead of downloading tarballs from one of the package archives. Doing so allows installing forked packages, altering local package checkouts, and locking packages to exact versions for reproducable setups.

Installation

When on Emacs 29 with the --with-native-compilation turned on, make sure you’re on straight.el’s development branch, as staying on the main branch breaks its bootstrap for now ((void-variable native-comp-deferred-compilation-deny-list)):

(setq straight-repository-branch "develop")

The Getting started section in the straight.el README provides the bootstrap code to place inside ~/.emacs.d/init.el in order to install it:

;; Install straight.el
(defvar bootstrap-version)
(let ((bootstrap-file
       (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
      (bootstrap-version 6))
  (unless (file-exists-p bootstrap-file)
    (with-current-buffer
	(url-retrieve-synchronously
	 "https://raw.githubusercontent.com/radian-software/straight.el/develop/install.el"
	 'silent 'inhibit-cookies)
      (goto-char (point-max))
      (eval-print-last-sexp)))
  (load bootstrap-file nil 'nomessage))

Straight.el uses package archives like GNU ELPA as registries to find the linked repositories to clone from. Since these are checked automatically, there’s no need to add them to the list of package archives.

While package.el loads all installed packages on startup, straight.el only loads packages that are referenced in the init file. This allows for installing packages temporarily without slowing down Emacs’ startup time on subsequent startups.

To create a truly reproducable setup, disable package.el in favor of straight.el by turning off package-enable-at-startup. Because this step needs to happen before package.el gets a chance to load packages, it this configuration needs to be set in the early init file:

;; Disable package.el in favor of straight.el
(setq package-enable-at-startup nil)

With this configuration set, Emacs will only load the packages installed through straight.el.

Usage

To use straight.el to install a package for the current session, execute the straight-use-package command:

M-x straight-use-package <RET> evil-commentary <RET>

To continue using the package in future sessions, add the straight-use-package call to ~/.emacs/init.el:

(straight-use-package 'evil-commentary)

To update an installed package, execute the straight-pull-package command:

M-x straight-pull-package <RET> evil-commentary <RET>

To update the version lockfile, which is used to target the exact version to check out when installing, run straight-freeze-versions:

M-x straight-freeze-versions <RET>

Use-package

Use-package is a macro to configure and load packages in Emacs configurations. It interfaces with package managers like package.el or straight.el to install packages, but is not a package manager by itself.

For example, when using straight.el without use-package, installing and starting evil-commentary requires installing the package and starting it as two separate steps:

(straight-use-package 'evil-commentary)
(evil-commentary-mode)

Combined with use-package, the installation and configuration are unified into a single call to use-package:

(use-package evil-commentary
  :config
  (evil-commentary-mode))

Aside from keeping configuration files tidy, having package configuration contained within a single call allows for more advanced package setups. For example, packages can be lazy-loaded, keeping their configuration code from executing until the package they configure is needed.

Installation

To install use-package with straight.el, use straight-use-package:

;; Install use-package
(straight-use-package 'use-package)

Using straight.el with use-package

By default, use-package uses package.el to install packages. To use straight.el instead of package.el, pass the :straight option:

(use-package evil-commentary
  :straight t)

To configure use-package to always use straight.el, use use-package to configure straight.el to turn on straight-use-package-by-default1:

;; Configure use-package to use straight.el by default
(use-package straight
  :custom
  (straight-use-package-by-default t))

Now, installing any package using use-package uses straight.el, even when omitting the :straight option.

Having both straight.el and use-package installed and configured to work together, the straight-use-package function isn’t used anymore. Instead, all packages are installed and configured through use-package.

Usage

Use the use-package macro to load a package. If the package is not installed yet, it is installed automatically:

(use-package evil-commentary)

Use-package provides keywords to add configuration, key bindings and variables. Although there are many more options, some examples include :config, :init, :bind, and :custom:

:config and :init

The :config and :init configuration keywords define code that’s run right after, or right before a package is loaded, respectively.

For example, call evil-mode from the :config keyword to start Evil after loading its package. To turn off evil-want-C-i-jump right before evil is loaded (instead of adding it to the early init file), configure it in the :init keyword:

(use-package evil
  :init
  (setq evil-want-C-i-jump nil)
  :config
  (evil-mode))
:bind

Adds key bindings after a module is loaded. For example, to use consult-buffer instead of the built-in switch-to-buffer after loading the consult package, add a binding through the :bind keyword:

(use-package consult
  :bin
  ("C-x b" . consult-buffer))
:custom

Sets customizable variables. The variables set through use-package are not saved in Emacs’ custom file. Instead, all custom variables are expected to be set through use-package. In an example from before, the :custom keyword is used to set the straight-use-package-by-default configuration option after loading straight.el:

(use-package straight
  :custom
  (straight-use-package-by-default t))

Summary

The resulting ~/.emacs.d/init.el file installs straight.el and use-package, and configures straight.el as the package manager for use-package to use:

(setq straight-repository-branch "develop")

;; Install straight.el
(defvar bootstrap-version)
(let ((bootstrap-file
       (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
      (bootstrap-version 6))
  (unless (file-exists-p bootstrap-file)
    (with-current-buffer
	(url-retrieve-synchronously
	 "https://raw.githubusercontent.com/radian-software/straight.el/develop/install.el"
	 'silent 'inhibit-cookies)
      (goto-char (point-max))
      (eval-print-last-sexp)))
  (load bootstrap-file nil 'nomessage))

;; Install use-package
(straight-use-package 'use-package)

;; Configure use-package to use straight.el by default
(use-package straight
  :custom
  (straight-use-package-by-default t))

The ~/.emacs.d/early-init.el file disables package.el to disable its auto-loading, causing all packages to be loaded through straight.el in the init file:

;; Disable package.el in favor of straight.el
(setq package-enable-at-startup nil)

This is the only configuration set in the early init file. All other packages are installed and configured through use-package, which makes sure to load configuration options before packages are loaded, if configured with the :init keyword.


  1. Calling use-package would normally install straight.el, but since it’s already installed, the installation is skipped and the configuration is set. Here, the call to use-package is only used to configure straight.el, by setting the straight-use-package-by-default option.

    ↩︎