🏠 About Now Archive Tags RSS

Emacs on Windows

Updated: November 23rd, 2025

I use Emacs as my text editor. At work, I use a laptop with Windows 11. After having tried a lot of different approaches, this school year, I just use Windows 11 on the laptop without any dual booting or additional desktop machines with GNU/Linux in classrooms or at my desk. When I need to show something in GNU/Linux to my students who use it on their desktops for parts of one of the subjects I teach, I will use Hyper-V on the laptop. This approach is simpler and more flexible than having multiple machines in different locations or dual-booting the laptop that I did in the past. There are also a few Windows-specific programs I need occasionally that are not available through Sharepoint on the web, and my students mostly use Windows 11 on their laptops so when showing them how to do something, it is better if I also use Windows, especially for things that happen in a non-cross-platform way, for example using Windows installers, Hyper-V or PowerShell in Windows-specific ways.

I use inter-present-mode in class for interactive presentations where I can run code blocks live, and write things into the presentation while presenting. Since I sometimes have pictures in my presentations and inter-present-mode changes face height, I need to use GUI Emacs. (Emacs is also available in the TTY and terminal emulators.) There are two ways to get GUI Emacs on Windows. The first is through WSL2 with the graphical features Microsoft calls WSLg.

Unfortunately, the Wayland stack is implemented in a non-standard way in WSLg that doesn't currently work properly. There are two bugs related to Wayland-native programs: the first bug makes Wayland native programs use XWayland instead of Wayland. The community found a fix for that bug in 2023 and told Microsoft about it in a GitHub Issues thread where a representative for Microsoft said they applied the fix to the codebase, but so far, Microsoft has not released that fix. You can fix it yourself if you like.

However, there is another bug that makes Wayland windows unresponsive after locking the screen and unlocking it again. This is the show-stopper for WSLg for me. (There is also a similar bug for X windows where the windows disappear after screen locking, but if you open a new X window, you can alt-tab to the original window and use it.)

Since I need GUI Emacs for presentations and thus cannot use Emacs in the Windows terminal through WSL2 without WSLg, and WSLg doesn't work properly, I have opted to instead use native Emacs on Windows. With WSL2, all the free software programs that integrate with Emacs is easily available, but I gradually discovered how to get the necessary external packages and how to configure Emacs on Windows to get as similar an experience as possible to the one I have on GNU/Linux. I wrote blog posts as I discovered these things, but I think it is more useful to have all the information in one post, rather than in separate posts, so I have deleted those older post and replaced them with this post instead.

I originally kept a separate Emacs configuration for Windows that I forked from my normal config, but since I always wanted to get things to work as similarly as possible on both OSes and I found it hard to keep the common parts in sync, I chose to integrate the two configs back into one with a few if and when-statements when needed. You can find it here.

People use Emacs in different ways, so you may find that you need something I don't, but I hope this blog post can give you a starting point. (It is also useful for me in case I need to set things up again.) Some other good starting points are Using Emacs on Windows 11: An Installation Guide by Dr. Peter Prevos and Properly Installing Emacs on Windows, Properly Setting up Emacsclient on Windows, Printing Directly from Emacs on Windows and Getting spelling to work in Emacs on Windows by Dr. Raoul Comninos on the EmacsElements website and YouTube channel. I have used their ideas in my setup, but tweaked things to my own liking.

Installation and basic use

I use the Windows installer from a nearby mirror of GNU.org as recommended on the Emacs website. Make sure to use runemacs.exe since it does not open an empty terminal before opening the Emacs GUI, unlike emacs.exe. It is a good idea to add runemacs.exe to your start menu and task bar to have it easily available. I have runemacs.exe as the leftmost icon in my task bar which means that I can launch it with Super-1.

You may also consider adding runemacs.exe as the default program to open .org, .md, .py, .js etc files. I find it easier to do so in File Explorer by right-cliking a file of that type and choosing Propteries… and setting the application to open that file type instead of using the Settings app which doesn't work by file type, but by program which always seems a bit backwards to me. The way I use Emacs, I tend to have a frame open at all times and visit files from inside Emacs, but when I use File Explorer, I often want files to open in other programs, usually because I want to show my students the files in the programs they use (Thonny, VSCode, Word, Excel, MySQL Workbench…) even if I usually would use the files of the same file types inside Emacs, so I have only set a few file types to open with runemacs.exe. Since I don't use emacsclientw.exe on Windows, I don't want to double click files in File Explorer or open them through the start menu since that opens a new Emacs frame if runemacs.exe is set to open those file types. I like to almost always work in just one Emacs frame.

I have stopped using Emacsclient on Windows since it doesn't work the same way as on GNU/Linux. It does not respect my settings for faces (fonts) or scroll bars from my config. If I open Emacsclientw on Windows directly, it complains that I haven't opened it with a file. On GNU/Linux, if I open Emacsclient without a file, it opens up with the scratch buffer. Since I want to have an Emacs frame open at all times and more frequently open files from inside Emacs than from the File Explorer, I quite often want to open emacsclientw without a file. Another weird thing is that when I kill the buffer with the file I opened emacsclientw with, Emacsclient quits. It doesn't do that on GNU/Linux. Since I want to keep the frame for further work through the day, this behaviour makes emacsclientw more or less unusable for me. The third annoyance is that if I forget to use M-x kill-emacs before restarting or turning off the Windows machine, it uses forever to turn off. Together, these annoyances were just too much, so I went back to open an Emacs frame after booting the machine and continue to use it until I turn the machine off.

Installation of tools that interact with Emacs

Emacs needs external programs for some of its functionality. Below is a table that shows useful programs I use on Windows to get the functionality I need or want from Emacs. More details can be found in the relevant sections below.

Package What it supplies Where to get it
chocolatey Package manager https://chocolatey.org/install Use Individual install.
Hack Free programming font https://github.com/source-foundry/Hack-windows-installer
Liberation Free variable pitch fonts Come with LibreOffice
imagemagick For showing pictures Install with Chocolatey
Pandoc Export org with ox-pandoc Install with Chocolatey
MikTex Export org via LaTex Install miktex.install with Chocolatey
Strawberry Perl MikTex needs Perl https://strawberryperl.com/
mupdf PDFs in doc-view-mode Install with Chocolatey
unoconv MS365 & ODF in doc-view-mode https://www.libreoffice.org/download/download-libreoffice/
git Version control https://git-scm.com/downloads
hunspell Spell checking program https://sourceforge.net/projects/ezwinports/hunspell-1.3.2-3-w32.bin.zip/download
nb_NO.aff, nb_NO.dic BokmÄl hunspell dictionary Come with Libreoffice
Node.js and npm NPM to install LSP servers https://nodejs.org/en/download
Python Pip to install LSP servers NPM installs Chocolatey and Python as well, otherwise Chocolatey
LSP servers Completions, errors, linting pip and/or npm
LibreOffice See above https://www.libreoffice.org/download/download-libreoffice/
Microsoft PowerToys Rebind keys (if needed) The Microsoft Store

Setup of environment variables

Emacs looks for a .emacs.d directory or a .config/emacs/ directory or a .emacs file in $HOME. To set $HOME, press the Windows key, write environment and you should see the Edit Environment Variables program (Rediger systemmiljÞvariabler if you use Norwegian BokmÄl). Set the User variables for HOME to wherever you want it. I also found that it was useful for git to set LANG and LC_ALL since git and other programs compiled with MSYS2 respects the text encoding from those. I did this as part of my trial and error process to find ways around git's default conversion of text encoding and line endings, but I am not certain it is necessary after setting the system locale to UTF-8 and/or setting Git Bash's text encoding in the GUI (see the dealing with text-encoding section for more on this).

windowsenv.jpg

Dealing with text-encoding

Windows uses UTF-16 under the hood, but on the surface it uses different text encodings depending on your locale. (GNU/Linux, BSD and MacOS use UTF-8 unless you configure them to use something else.) Windows uses CR and LF to end lines while GNU/Linux, BSD and MacOS X use LF. (Mac OS < 10 used CR.) There are different strategies to deal with this:

  1. Use the locale's encoding on Windows and have git convert line endings on clone, pull and push.
  2. Use UTF-8 and Unix line endings for every buffer and file inside Emacs, but configure Emacs to work with yanking (pasting) and killing (cutting) to and from the clipboard in Windows locale's text encoding. Tell git to not convert files and line endings.
  3. Set Windows 11 to use UTF-8, set Emacs to use UTF-8 and Unix line endings and tell git to use UTF-8 and not convert line endings.

If you do 1, then you don't have to configure git, because that is its default behaviour on Windows. This supposes that you use your text editor(s) with Windows line endings, and possible also your locale's text encoding which makes writing a lot of glyphs, like emojis, Hangul, Hiragana etc, impossible. Even VSCode defaults to UTF-8 and Unix line endings, even on Windows. I think there is absolutely no advantages to this approach unless you never use any other platform than Windows.

If you do 2, then you have files with only UTF-8 and Unix line ending in your repos, but you have to configure Emacs to convert text when yanking and killing to/from the system clipboard. You also have to configure git not to convert CRLF to LF. (See the below section on how to do this.) Below is how you configure yanking and killing to work to and from iso-latin-1 which is the common text encoding for Windows in Northern Europe and North America. I tried this approach for a while, but would rather avoid another if-statement in my config if I could avoid it to keep things as similar as possible between Windows and GNU/Linux.

(if (eq system-type 'windows-nt)
   (set-clipboard-coding-system 'iso-latin-1)
  (set-clipboard-coding-system 'utf-8))

My strategy

I use the third strategy now. Windows 11 has a beta feature where you can choose to use UTF-8. You can set it up by following the step-by-step instruction from geekrewind.com. You still have to configure git not to convert CR LF to LF and vise versa on push and pull, but you don't have to configure Emacs to do anything when yanking and killing. To configure git to use UTF, I set the user environment variables LANG and LC_ALL since git and other programs compiled with MSYS2 respects the text encoding from those. In addition, I opened Git Bash, right-clicked the window title, selected Options…, selected Text, chose nb_NO and UTF-8 (you may want something else than nb_NO which is Norwegian BokmĂ„l) since I read somewhere that git uses the settings set in Git Bash. I am not certain if you need to do both, but I did and it works. In my .gitconfig in my $HOME, I made a section called [core] and on the next line wrote autocrlf = false. This means that git will not convert line endings on push and pull from Unix (LF) to Windows (CRLF). Since all my files use Unix line endings anyway, this is no longer necessary.

I still have configuration to always use UTF-8 and Unix line endings inside Emacs and save every file as UTF-8 with Unix line endings. This is useful for getting rid of other encodings in previously made files. (In case I want that other encoding, for example for files I want to use under emulation of older systems, I can omit saving and they would still be in their original encoding.) I hope Microsoft will make UTF-8 and Unix line endings standard in Windows 12. The only small problem I have seen since switching to UTF-8 is that a few Norwegian glyphs in HP's notification about driver updates don't display properly. Otherwise, I have had no problems. Below is the configuration I use to set Emacs to use UTF-8 and Unix line endings and write every file with UTF-8 and Unix line endings.

(prefer-coding-system 'utf-8-unix)
(set-default-coding-systems 'utf-8-unix)
(set-language-environment 'utf-8)
(setq-default coding-system-for-write 'utf-8-unix)
(setq-default buffer-file-coding-system 'utf-8-unix)

Spell checking

I used to use aspell with aspell-en and aspell-nb (or -no) depending on whether the distro ships both Nynorsk and BokmÄl in the same package or have separate packages (nb = Norwegian BokmÄl, nn = Norwegian Nynorsk, no = both Nynorsk and BokmÄl). I did not find aspell-no or nb on chocolatey or msys2 and I had the same problem with the hunspell package available for Windows which comes with British and American English which I sometimes use, but not Norwegian BokmÄl.

For a while, I turned off spelling on Windows, but I recently looked into it again and found that I could copy over the .dic and .aff files I get with LibreOffice since it uses hunspell as well. The LO installer installs Norwegian dictionaries with a "typical install" if you downloaded the installer by browsing to the download page with a Norwegian IP address. To use the dictionaries from LibreOffice, go to C:\Program Files\LibreOffice\share\extensions\dict-no\ and copy the files nb_NO.aff and nb_NO.dic. If you use another language, have a look in the extensions folders to find the folder with your relevant dictionaries. Go to the share\hunspell directory under where you installed hunspell. I use C:\Program Files\Hunspell\share\hunspell\ and paste the files there.

In addition I have taken the settings suggested by Getting spelling to work in Emacs on Windows and modified them slightly for my use. I now also use hunspell on GNU/Linux. I have also made a convenient function in my Emacs config to toggle between Norwegian BokmÄl and British English, and bound it in my keymap to C-z i.

(use-package flyspell
  :hook ((prog-mode . flyspell-prog-mode)
         (text-mode . flyspell-mode))
  :init
  (when (eq system-type 'windows-nt)
    (setq ispell-program-name "C:/Program Files/Hunspell/bin/hunspell.exe"))
  (setq ispell-dictionary "nb_NO")
  (setq ispell-dictionary-alist
    '(("nb_NO" ":alpha" "[^[:alpha:]]" "[']" nil ("-d" "nb_NO") nil utf-8)))
  (setq hunspell-default-dict "nb_NO"))

Dictionary lookup

On my GNU/Linux systems, I install a dictd server and some dictionaries so I don't need to be connected to the internet to use dictionary-lookup-definition. Since there is no dictd server package for Windows, I first tried to use dict.org to look up words. When I tried dictionary-lookup-definition, Emacs just froze. It did not use a lot of CPU, but it just did not respond do anything, including C-g. I had to kill it with the three-finger salute (Ctrl-Alt-Delete). Maybe the relevant ports are blocked by my network at work, so it might work for you even if it did not for me. Below, you see my configuration for dictionary mode. I check if I am on GNU/Linux and otherwise, don't load it since I cannot get it to work at work.

(use-package dictionary
   :if (eq system-type 'gnu/linux)
   :config
   (setq dictionary-server "localhost"))

Viewing MS365 and Open Document Format documents

Emacs can show .xlsx, .docx, .pptx, .odt, .odp and other MS365 (Formerly Microsoft Office) and LibreOffice (Open Document Format) files in doc-view-mode. It is very practical to go directly from dired to doc-view-mode in stead of waiting for LibreOffice or MS365 to start. To show these files it needs a python script called unoconv that comes with LibreOffice. You also need to set the path to that script to get it to work. Since newer versions of LibreOffice stalls when you open them on Windows 11, either if you launch Writer or use unoconv from Emacs, I had to install version 24.2.0.3 to get this to work properly. You find it with the Archive link on the LibreOffice download page.

Pandoc and LaTex for export and import from Org mode

I use pandoc with Emacs and also ox-pandoc to be able to export from Org mode to .docx format. Upper Secondary schools in Norway use MS365, so it is useful to be able to write .docx documents occasionally. I use LaTex a lot for export of PDFs from org files I have used with inter-present-mode in class. Both pandoc and LaTex can be installed with Chocolatey, but when using LaTex to export PDFs on Windows 11, I always got an error that wrapfig.sty was not installed. It probably doesn't come with the LaTex package from Chocolatey. The solution is to use MikTex instead. Install the package miktex.install with chocolatey. MikTex needs Perl to function, so you also need to install Strawberry Perl from its website to get the export functionality to work. As with any external programs, you also need to set the exec paths (see further down this blog post) to get the actual functionality, unlike on GNU/Linux where it just works.

PowerShell in Shell mode

Since I like to have as few windows open as possible since Windows ironically is terrible at handling windows (at least compared to the tiling window manager Sway which I use on GNU/Linux), it makes more sense to use PowerShell in shell mode and stay within Emacs instead of launching a new PowerShell window. (Some people think window snapping solves the inefficiencies of a floating window manager or desktop environment, but it doesn't since you have to do it manually with a keyboard shortcut or even more inefficiently by mousing around and dragging the windows to the edges. A tiling WM tiles automatically without any need to mouse around or press keyboard combos, which saves time and interruptions.)

By default, you get cmd.exe in Shell mode on Windows. I had some trouble finding good documentation of how to set it up with PowerShell. Some people suggest just setting PowerShell to explicit-shell-file-name, but that only partially worked since it gave me an error that PowerShell was invoked with a wrong argument. A lot of people pointed to someone on the Microsoft blogs that set up PowerShell in Shell mode, but in a way that removed the prompt. He had made an Elisp workaround to get the prompt back, but it seemed like there should be an easier way. The trouble is that comint mode, the mode shell mode is built on, sends -i by default to the shells it interacts with as an argument. I tried a couple of things and it turns out the solution is really simple.

(use-package shell
  :config
  (add-hook 'shell-mode-hook 'ansi-color-for-comint-mode-on)
  (add-to-list 'comint-output-filter-functions 'ansi-color-process-output)
  (add-hook 'comint-output-filter-functions 'comint-osc-process-output)
  (add-hook 'shell-mode-hook (lambda() (company-mode 0)))
  (when (eq system-type 'windows-nt)
    (setq explicit-shell-file-name "C:/Windows/System32/WindowsPowerShell/v1.0/powershell.exe")
    (setq explicit-powershell.exe-args '("")))
  (when (eq system-type 'gnu/linux)
     (setq explicit-shell-file-name "/bin/bash")
     (setq system-uses-terminfo t)
     (setq comint-terminfo-terminal "xterm")))

In the code block above, if on Windows, I tell Emacs to run PowerShell and give it the arguments "" (ie, nothing) which remove the default -i argument, but adds nothing else. And now I have PowerShell in Shell mode. The rest of the configuration turns off company since the shells have their own tab-completion and turns on ansi-colours which both Bash and PowerShell can work with. If you prefer to use PowerShell 7 instead, use the path to it instead of the path to the built-in Windows PowerShell.

Language Server Protocol servers

LSP is a standard Microsoft made for adding language support to VSCode that is now also used by other editors like NeoVim and Emacs. In Emacs, you just have to have the LSP servers you want installed, the correct paths configured and eglot, company and eldoc, if turned on, makes use of them in the mode for the associated programming or markup language. I tend not to like documentation constantly popping up with eldoc-mode, but use completions and warnings from eglot and company. Below is a table of the LSP servers I use and how to install them on Windows.

Language LSP-server names install with Comment
Python python-lsp-server (pylsp) pip or npm i -g FOSS, flake8 is an optional dependency
Bash / Shell shellcheck, bash-language-server npm i -g FOSS
JavaScript, TS, JSX typescript-language-server, typescript npm i -g Microsoft, LSP depends on typescript
CSS vscode-css-languageserver-bin npm i -g Microsoft
HTML vscode-html-languageserver-bin npm i -g Microsoft
JSON vscode-json-languageserver npm i -g Microsoft

The Microsoft LSP servers give you the same completions, documentation and warnings as in VSCode. I would recommend against Microsoft's pyright which has given both me and my students errors that were not actual errors. For instance, it told me that I could not change data type for an element inside a list even if that is precisely what a dynamically typed language like python lets me do. I used the list internally in a function, so it was not like I was mutating a list I might use somewhere else in the file. Python-lsp-server, also called pylsp has never given me any errors that weren't actual errors. You may find more LSP servers at https://langserver.org/.

In addition, you have to turn eglot on in the relevant modes. I turn it on for all programming modes.

(use-package eglot
  :hook (prog-mode . eglot-ensure))  

Python Shell

Python mode in Emacs on GNU/Linux works out of the box with no setup if you have Python installed on your system. However, on Windows, Python mode does not work even if Python is installed. It does not help to add the directory Python is in to the exec-path or add it to the $PATH environment variable either. After trying those two things, I looked for a useful variable to set the path to the python executable and tried a couple of promising ones before finding the correct solution. In the code below, I set the docstring-style to Django which I prefer over the default pep-257 and then check if on Windows and if so, set the path to the shell to where I have python installed.

(setq python-fill-docstring-style 'django)
(when (eq system-type 'windows-nt)
  (setq python-shell-interpreter "c:/Python313/python.exe"))

Rebind keys

After realising that using a key only available in the Emacs GUI, but not when using Emacs in a TTY or a graphical terminal, was dumb, I changed my prefix key for my keymap to C-z and no longer rebind CapsLock to <apps/menu> on Windows with PowerToys and <menu> in my Sway config on GNU/Linux. I use Lenovo ThinkPads and keyboards at home, so I had to switch Fn and Ctrl on my HP work laptop with its BIOS to not confuse my muscle memory when at work. If you need to rebind keys on Windows, and cannot do it in the BIOS of your computer, try PowerToys. You can get it in The Microsoft Store.

Paths that need adding

For Emacs to integrate with external packages on Windows, the exec path needs to include the file path to those packages. Below is my extra configuration for adding exec-paths in Windows. You may have placed the programs you have installed manually, like Hunspell, in other places, so adjust accordingly. You may also do this with customize, but I want to have it as part of my config since sometimes I let the custom-vars file where I keep the changes from customize drift a little bit away from the upstream emacs_config repo on some machines.

(when (eq system-type 'windows-nt)
  (custom-set-variables
 '(exec-path
   '("C:/Program Files/MiKTeX" "C:/Program Files/ImageMagick-7.1.2-Q16-HDRI" "C:/Python313/"
     "C:/ProgramData/chocolatey/lib/findutils/tools/install/bin/" "C:/WINDOWS/system32"
     "C:/WINDOWS" "C:/WINDOWS/System32/Wbem" "C:/WINDOWS/System32/WindowsPowerShell/v1.0/"
     "C:/WINDOWS/System32/OpenSSH/" "C:/Program Files/dotnet/" "C:/Program Files/Git/cmd"
     "C:/Program Files/nodejs/" "C:/ProgramData/chocolatey/bin" "C:/ProgramData/chocolatey/lib/mpv/"
     "C:/ProgramData/chocolatey/lib/mupdf" "C:/Users/AFK01217/AppData/Local/Microsoft/WindowsApps"
     "C:/texlive/2025/bin/windows" "C:/Users/AFK01217/AppData/Local/Programs/Microsoft VS Code/bin"
     "C:/Users/AFK01217/AppData/Roaming/npm" "C:/Users/AFK01217/AppData/Local/Pandoc/" "."
     "C:/Program Files/Hunspell/bin" "c:/Strawberry/perl/bin/" "C:/Program Files/LibreOffice/program"))
 '(image-load-path
   '("c:/Program Files/Emacs/emacs-30.1/share/emacs/30.1/etc/images/" data-directory load-path "c:/python313/")))
)
© Einar Mostad 2010 - 2025. Content is licensed under the terms of CC BY SA except code which is GNU GPL v3 or later.
Made with GNU Emacs, Org-Static-Blog, and Codeberg Pages on GNU/Linux.