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
- ESP-specific
clangdis installed viaidf_tools.py install esp-clang - 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
}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=**",
},
}| Command | Description |
|---|---|
:ESPReconfigure |
Runs idf.py -B build.clang -D IDF_TOOLCHAIN=clang reconfigure |
: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:
- Use a Nix flake (recommended, see below)
- Or manually source
~/esp/esp-idf/export.shbefore launching Neovim
Clone and install ESP-IDF:
mkdir -p ~/esp
cd ~/esp
git clone --recursive https://github.com/espressif/esp-idf.git
cd esp-idf
./install.sh esp32c3Install the Espressif-specific clangd:
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 flashUsing nix is highly recommended. Use this flake.nix to create a reproducible ESP32 development environment:
{
description = "Development ESP32 C3 with ESP-IDF";
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};
in {
devShells.default = pkgs.mkShell {
buildInputs = with pkgs; [ cmake ninja dfu-util python3 ccache ];
shellHook = ''
. $HOME/esp/esp-idf/export.sh
'';
};
});
}Then use direnv with a .envrc:
touch .envrc
echo 'use flake' > .envrc
direnv allowThis will automatically load the environment when you enter the directory. β Now Neovim and the plugin will inherit the full ESP-IDF toolchain environment.
MIT License Β© 2026 Aietes