Saturday, 7 February 2026
It is about five years ago that I submitted a package called setup to GNU ELPA1. The premise was simple, I had grown dissatisfied with use-package and wanted to implement my own “configuration macro” that would be a better fit for packages written in a good style.
In this post I’d like to look back on the package and the macro, to chronicle my experience and the lessons I took away.
use-packageAmong people who like Emacs, especially those who avoid learning to use Emacs by configuring it, it is popular to recommend use-package when configuring Emacs. The idea is that you can avoid a lot of repetitive idiosyncrasies by instead having a macro translate a declarative-esque DSL into the right code. It also allows the user to request that a package be installed at startup if it hasn’t yet been installed.
It was written by the renowned John Wiegley around the early 2010’s — which is important because the context of the time is an important influence on the features it implements: Keywords like :autoload, :mode :interpreter or :magic are not necessary if working with Emacs packages that have properly configured autoloads and use these to adjust variables like auto-mode-alist. I also assume that the default behaviour, not to install packages, goes back to this.
Totalling at around 3500 lines of Elisp it is also not trivial to keep an overview of the code, and in my opinion it is also more tricky to extend with new keywords — at least compared to the ease at which you can define a new command.
Finally, something I never managed to figure out how to reasonably have a consistent configuration that would also use use-package for built-in, non-packages. You can add these kinds of configurations to the pseudo-package emacs, but that felt like a kludge to me.
setupSuperficially the syntax of setup is different but not a radical departure from the use-package approach. Instead of using keywords like
(use-package flymake
:bind ("M-n" . flymake-goto-next-error))
you would use a “local” macro, bound at compile-time around the setup form
(setup flymake
(:bind "M-n" flymake-goto-next-error))
Note that the fact that the function symbol is a keyword is in no way significant. We could have also used &bind.
There were a few ideas and goals underlying the design:
It should be easy to add a new local macro or change the behaviour of an existing one. I created a EmacsWiki page for people to collect ideas. For the sake of convenience, a function setup-define helps set up a local macro and provides a few shorthands to enable implicit looping and coercion. As a side-effect, you also get Xref integration on local macros!
The setup macro is not declarative! Therefore it is nice if you can macro-expand the form and see what is going on. Take the two examples from above2:
;; `use-package'
(progn
(defvar use-package--warning2
(function
(lambda (keyword err)
(let
((msg
(format "%s/%s: %s" 'flymake keyword
(error-message-string err))))
(display-warning 'use-package msg :error)))))
(condition-case-unless-debug err
(progn
(unless (fboundp 'flymake-goto-next-error)
(autoload (function flymake-goto-next-error) "flymake"
nil t))
(bind-keys :package flymake ("M-n" . flymake-goto-next-error)))
(error (funcall use-package--warning2 :catch err))))
;; `setup'
(eval-after-load 'flymake
(function
(lambda nil
(define-key flymake-mode-map [134217838]
(function flymake-goto-next-error)))))The local macros use a separate, lexical scope to determine exactly what to expand to. Conventionally, the local context consists of the feature being configured (useful when wanting to generate code that should only be evaluated after the respective feature has been loaded), the current keymap, mode-hook, etc.
Here are a few examples from my init.el that I consider to demonstrate that applying these principles results in easy to read and write code:
(setup (:package writegood-mode)
(:hook-into text-mode))
(setup (:package do-at-point)
(:bind-to "C-'"))
;; slightly simplified
(setup (:and (executable-find "go")
(:if-package go-mode))
(setopt gofmt-command "goimports"
gofmt-show-errors nil)
(:local-hook before-save-hook gofmt)
(:local-set compile-command "go build && go tool vet"
tab-width 4)
(:hook subword-mode))
For further details on the macro, I would recommend going to the EmacsWiki page linked above. The technical details are not of primary interest in this post.
setupHere I’d like to list a few events and comments in roughly chronological order:
Upon submitting the package to GNU ELPA, it was Stefan Monnier who made a few important and very helpful comments that influenced critical features of the package. For this, and all advice since, I remain extremely grateful!
The macro was featured in a stream later that year (2021) by David Wilson, on alternatives to use-package.
Many people were upset over the name setup, and tried to get me to rename the package; I on the other hand loath (package) names that were obviously just chosen to be easy to look up on a search machine.
The package was surprisingly popular on /g/. Some nice quotes:
The last quote references to a major development in Emacs 29, that use-package, which had a tricky copyright situation until then, was added to Emacs OOTB. I actually ended up helping out with the process, though it was mainly driven by Payas Relekar and Stefan Kangas. By this move, setup lost an edge since before you first had to enable MELPA (later NonGNU ELPA, if it wasn’t already enabled) to install use-package, while now you can just use use-package directly.
During the use-package merge discussion I had an exchange with John Wiegley where I demonstrated how an auxiliary macro could translate a use-package-like syntax into setup to simplify the processing. In the end the idea didn’t go anywhere.
I had a number of nice conversations with people who liked the macro, and multiple contributions by Okamsn (maintainer of the loopy macro, which I don’t think is coincidental).
There haven’t been many changes for the last two years. The last local macro I added was in 2024 (over two years ago at this point) and I have been deprecating more stuff since then (mostly macros like :option that are not context-specific and are better replaced using a more general macro like setopt), but haven’t removed it yet.
This is both a sign of decline and stability. While fewer people are using it, the fact that no further changes have been necessary is a sign that it does what it should. There are a few things I might change if setup is ever bundled with Emacs, for instance I might want to deprecate :when-loaded (the analogue of :config in use-package) because I think that anything you’d write within this local macro should either be replaced by a specific local macro or is a bad upstream design of the package maintainer.
setupI have to admit I am not as enthusiastic about the macro as I was 2021. It is not that I dislike it, the implementation still seems good and I enjoy reading it, but I am mostly indifferent about the need for configuration macros in general.
Over the last two years I have configured Emacs from start on two work machines, and both times I was more than just fine to append stuff to my init.el and use Easy Customization Interface. At the same time I continue to use setup on my personal machine, and as I grow ever more convinced of the fact that (obsessive) configuring of Emacs is a sign of a fatal misunderstanding of Emacs on some level, I don’t have the motivation to rewrite it because I don’t really have anything to gain from it. Most of the times I touch my init.el is to bind a new command, set a new user option or in the best case delete some code if an analogous feature has been upstreamed.
I don’t really know who uses setup anymore, but I am aware of people who have used it in the past and given up. Interesting nobody points me to a specific reason or issue, but my impression is that most people run into a similar kind of lethargic apathy, but decide to jump to something else instead (again, since the upstreaming of use-package I myself have considered switching a few times to simplify my configuration, but never gone through with it).
Finally, I recently suggested replacing the idea of setup macros with a more non-Lisp’ish configuration syntax, and explained why in the aforelinked article. I haven’t continued developing the proposal, though I still think it is more user friendly to someone who prefers to think of configuring Emacs not as programming with a understanding of what is going on but as toggling the right knobs to get the intended behaviour.
So the future is certain and reliable that the macro will stick around, and I guess I’ll keep on maintaining it?