ESP32.nvim makes working with ESP-IDF projects in Neovim a breeze.
Designed for a smooth ESP-IDF workflow inside Neovim and LazyVim. Uses snacks.nvim for terminal and picker UIs.
- π§ Automatically detects ESP-IDF-specific
clangd - π Configures
build_dir(build.clang) for IDF builds - π₯οΈ Launch
idf.py monitorandidf.py flashin floating terminals - π Pick available USB serial ports dynamically on macOS and Linux
- π Check project setup with
:ESPInfo - π Quickly run reconfigure with
:ESPReconfigure - βοΈ Provides LSP configuration for ESP-IDF projects
- π§ Supports extra
clangdarguments for advanced toolchain setups
- ESP-IDF installed and initialized. The recommended upstream setup is now ESP-IDF Installation Manager.
- ESP-specific
clangdis installed. With ESP-IDF Installation Manager, includeesp-clangin the selected tools, or runidf_tools.py install esp-clangfrom an activated ESP-IDF shell. - ESP-specific
clangdis configured viaidf.py -B build.clang -D IDF_TOOLCHAIN=clang reconfigure(can be done via command:ESPReconfigure) - snacks.nvim (automatically installed via LazyVim dependencies)
Install via Lazy.nvim or any other plugin manager. Via Lazy.nvim:
{
"Aietes/esp32.nvim",
}When installed through Lazy.nvim, the plugin ships a packaged lazy.lua spec. That means Lazy.nvim users automatically get:
- the
snacks.nvimdependency - the default
build_dir = "build.clang"option - the default ESP32 keymaps
The packaged default keymaps are:
<leader>Rb: build<leader>RM: pick and monitor<leader>Rm: monitor<leader>RF: pick and flash<leader>Rf: flash<leader>Rc: menuconfig<leader>RC: clean<leader>Rr: reconfigure<leader>Ri: project info
ESP32 commands open in a floating terminal, which automatically closes when the command is done. For long-running commands like monitor and menuconfig, the terminal stays open until you close it:
- Press
qto close the terminal window (idf.py monitorkeeps running when closed withqand you can reattach to it later) - Press
Ctrl + ]to stop the running process and close the terminal window
opts = {
build_dir = "build.clang", -- directory for CMake builds (must match your clangd compile_commands.json)
clangd_args = {}, -- optional extra clangd arguments
idf_cmd = nil, -- optional idf.py command override
}To customize the packaged defaults, override them in your own spec:
{
"Aietes/esp32.nvim",
opts = {
build_dir = "build.custom",
clangd_args = {
"--query-driver=**",
},
},
keys = {
{ "<leader>em", function() require("esp32").pick("monitor") end, desc = "ESP32: Pick & Monitor" },
},
}
β οΈ Attention: To get code completion and diagnostics working correctly, the LSP must be configured properly. This plugin provides the required LSP configuration throughrequire("esp32").lsp_config(). You need to hook that into your Neovim LSP setup in one of the two ways below.
If you use LazyVim, add this to your nvim-lspconfig spec, LazyVim will take care of the rest:
{
"neovim/nvim-lspconfig",
opts = function(_, opts)
opts.servers = opts.servers or {}
opts.servers.clangd = require("esp32").lsp_config()
return opts
end,
}If you manage your LSP setup manually, include the LSP config from this plugin directly where it fits in your setup:
vim.lsp.config("clangd", require("esp32").lsp_config())
vim.lsp.enable("clangd")This plugin exposes the required LSP setup through:
require("esp32").lsp_config()That configuration:
- points the LSP at your configured
build_dir - prefers
sdkconfigandCMakeLists.txtas root markers so nested ESP-IDF projects do not attach to a parent git repository by accident
If you need additional clangd flags for your environment, you can pass them through clangd_args:
opts = {
build_dir = "build.clang",
clangd_args = {
"--query-driver=**",
},
}By default, ESP32.nvim runs idf.py from the active ESP-IDF environment. If idf.py is not an executable on PATH, the plugin can also use an activated EIM environment by running:
$IDF_PYTHON_ENV_PATH/bin/python $IDF_PATH/tools/idf.pyThis handles EIM shells where idf.py is provided as a shell function instead of a standalone executable. If your setup needs a custom wrapper, configure idf_cmd:
opts = {
idf_cmd = "/path/to/idf.py-wrapper",
}| Command | Description |
|---|---|
:ESPReconfigure |
Runs ESP-IDF reconfigure with -B build.clang -D IDF_TOOLCHAIN=clang |
:ESPInfo |
Shows ESP32 project setup info |
:ESPBuild |
Runs a build of the project |
pick |
Pick a serial port and run a command on it. Remembers the selected port for later commands. |
command |
Run a command, reusing the last selected port when available. |
The plugin defines the user commands above automatically. When installed through Lazy.nvim, the packaged lazy.lua also provides the default keymaps listed above.
- This plugin does not install ESP-IDF automatically, see the recommended setup below
- You must either:
- Source an ESP-IDF environment before launching Neovim
- Or use a project-local Nix/direnv shell to source that environment for you
Espressif now recommends ESP-IDF Installation Manager (EIM) for new setups. On macOS:
brew tap espressif/eim
brew install eim
eim install -i v5.5 --idf-tools=esp-clangOn Linux, use the package source recommended by Espressif for your distribution, or install EIM with Homebrew:
brew tap espressif/eim
brew install eim
eim install -i v5.5 --idf-tools=esp-clangAfter installation, activate the generated ESP-IDF environment before launching Neovim. EIM creates a per-version activation script under ~/.espressif/tools:
source ~/.espressif/tools/activate_idf_v5.5.shThen make sure the Espressif-specific clangd is installed. If you did not select esp-clang during the EIM install, run:
idf_tools.py install esp-clangCreate your build directory using clang:
idf.py -B build.clang -D IDF_TOOLCHAIN=clang reconfigureFrom now on, always build and flash using:
idf.py -B build.clang build
idf.py -B build.clang flashIf you prefer the classic manual ESP-IDF clone, that still works:
mkdir -p ~/esp
cd ~/esp
git clone --recursive https://github.com/espressif/esp-idf.git
cd esp-idf
./install.sh esp32c3
source ~/esp/esp-idf/export.shThen follow the same esp-clang and build.clang steps above.
EIM is the recommended way to install and manage ESP-IDF. If your project already uses nix and direnv, you can use a flake to activate an existing EIM or manual ESP-IDF install when you enter the project.
This does not install ESP-IDF with Nix; it only makes Neovim inherit the same ESP-IDF environment every time the project shell loads.
{
description = "ESP-IDF activation shell";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system};
idfVersion = "v5.5";
eimActivation = "$HOME/.espressif/tools/activate_idf_${idfVersion}.sh";
manualActivation = "$HOME/esp/esp-idf/export.sh";
in {
devShells.default = pkgs.mkShell {
buildInputs = with pkgs; [ cmake ninja dfu-util python3 ccache ];
shellHook = ''
if [ -f "${eimActivation}" ]; then
. "${eimActivation}"
elif [ -f "${manualActivation}" ]; then
. "${manualActivation}"
else
echo "ESP-IDF activation script not found." >&2
echo "Run: eim install -i ${idfVersion} --idf-tools=esp-clang" >&2
fi
'';
};
});
}Then use direnv with a .envrc:
touch .envrc
echo 'use flake' > .envrc
direnv allowThis will automatically activate the existing ESP-IDF environment when you enter the directory. β Now Neovim and the plugin will inherit the full ESP-IDF toolchain environment.
MIT License Β© 2026 Aietes