Over the years, I’ve noticed common issues in the private configs of Doom’s users. Here, I document those that stand out most (and are caused by misunderstanding rather than bugs), with hopeful solutions. Many of these cause subtle issues that can lead to misbehavior down the road.
The problem: you may be surprised that this hook never runs:
(add-hook! 'python-mode-hook (lambda () (setq python-indent-offset 2)))
Be careful with the differences between
add-hook and Doom’s
add-hook! convenience macro.
add-hook! will implicitly wrap list arguments in
(lambda (&rest) ...). What the above actually ends up doing is:
(add-hook 'python-mode-hook (lambda (&rest _) (lambda () (setq python-indent-offset 2))))
The solution: Either use
add-hook (which accept the lambda the way you were expecting), or remove the lambda and use
(add-hook 'python-mode-hook (lambda () (setq python-indent-offset 2))) ;; or (add-hook! 'python-mode-hook (setq python-indent-offset 2))
The problem: the first argument of the
after! macro is not a major or minor mode, but the name of a package. For example:
;; Do (after! python ...) (after! flycheck ...) ;; Don't (after! python-mode ...) (after! flycheck-mode ...)
The latter is a common mistake; it is never evaluated, and users will wonder why their code isn’t having an effect.
The solution: Use the package’s name, instead. What package to use can be determined using Doom’s documentation facilities:
python-mode(for users with evil disabled)
These display the documentation for
python-mode in a popup. The first line of the documentation will indicate the package that defines it:
python-mode is an autoloaded and interactive function defined in python.el.gz.
python.el.gz is the package’s file name. Strip away the extensions and you have the package’s name (i.e.
To add to the confusion: some packages are named after a mode they define. e.g. The
js2-mode. In this case,
(after! js2-mode ...)would be correct.
The problem: Doom lazy loads (almost) all of its packages, i.e. they are not loaded immediately, but when you need them. A common mistake is to misuse the
use-package!) macros, causing packages to be eagerly loaded.
(use-package! X :config ..)
The expectation is that
... is evaluated later, when
X is loaded, but what actually happens is
X is immediately loaded at startup (then
... is evaluated).
(use-package! X :defer t :config ...) ;; or (after! X ...)
use-packagehas other properties that imply
:defer t. e.g.
The problem: Your
PATH and other environment variables are sometimes unavailable in GUI Emacs (this is especially true for macOS users). The exec-path-from-shell package was written to correct this, by launching your shell and scraping its environment when you start up Emacs.
So what’s the problem? Doom comes with its own, better version of this. If you weren’t aware, you’ll likely benefit from switching to it. I go into further detail elsewhere.
The solution: Run
doom env in the command line instead. This scrapes your current shell environment into an envvar file which Doom loads at startup.
You only need to do this once. Your envvar file is regenerated each time you
doom sync thereafter.
The problem: Doom has its own package manager (powered by straight.el). It is far more powerful than the package manager built into Emacs (called
However, most package documentation will tell you to install packages with
M-x package-install or with an
:ensure t in a
use-package block. Doom won’t stop you from doing so if you know what you’re doing, but in most cases, the user simply isn’t aware of Doom’s package manager.
The fix: Use Doom’s package manager instead. This section in the manual about package management goes into the details, but the highlights are:
- To install a package: add
~/.doom.d/packages.el, then execute
$ doom syncon the command line.
- Do not use
:ensure tin your
(use-package ...)declarations, as it uses package.el under the hood
$ doom sync -uif you’ve changed the recipe of an existing package and want to update your packages.
The problem: You might have needed something like this in your previous config:
(org-babel-do-load-languages 'org-babel-load-languages '((R . t) (python . t)))
This practice is recommended in the documentation of by many Org babel plugins, but this is unnecessary in Doom Emacs. Babel has been modified to lazy-load your babel plugins. No additional configuration is needed on your part unless the package has special load requirements (such as
jupyter – which Doom will set up for you with
The solution: remove calls to
org-babel-do-load-languages in your config.
The problem: Users may be calling
(server-start) themselves, in their config. This isn’t necessary, because Doom starts the server for you, after a brief delay, after your first input, or when you first unfocus Emacs.
The solution: Don’t start it yourself.