Phase 2: Setup & Development 18 min read read

Installing ESP-IDF: Native Espressif Development Framework Setup Guide

Install ESP-IDF on Windows, macOS, and Linux for native FreeRTOS ESP32 development. Covers toolchain setup, environment variables, your first IDF project, and VS Code integration.

Updated June 18, 2026

Why Learn ESP-IDF?

The Arduino framework is an excellent starting point for ESP32 development. Its abstractions are consistent, its community libraries are vast, and a working project can be running within minutes. But Arduino is a layer built on top of Espressif’s own native SDK — the ESP-IDF. Understanding and using IDF directly unlocks features that Arduino does not expose: fine-grained FreeRTOS task management, hardware security engines, symmetric multiprocessing across both cores, JTAG debugging, the full NVS API, partition table customisation, and the latest Matter/Thread connectivity stacks.

This guide walks through installing ESP-IDF on all three major platforms, setting up the environment, and building your first native IDF “Hello World” project. It is a longer process than installing Arduino, but the setup is stable — once done, it changes rarely.

Prerequisites

Before starting, ensure you have: a stable internet connection (15–20 GB download), Git installed (git-scm.com), Python 3.8+ installed (python.org), at least 20 GB free disk space, and the USB driver for your ESP32 board already installed (see the Arduino IDE setup guide for driver details). On Windows, also install CMake and Ninja from their official websites — the IDF installer script will prompt you if they are missing.

Installing on Windows

Espressif provides an all-in-one Windows installer that handles most dependencies automatically. Go to github.com/espressif/esp-idf/releases and download the latest stable esp-idf-tools-setup-x.x.x.exe file. Run it as administrator. The installer will:

  1. Let you choose the ESP-IDF version to install (choose the latest stable v5.x)
  2. Download and extract ESP-IDF to C:Espressifframeworksesp-idf-v5.x
  3. Download and install the Xtensa and RISC-V GCC toolchains
  4. Install Python and create a virtual environment with all IDF dependencies
  5. Install CMake, Ninja, and OpenOCD for JTAG debugging
  6. Add a shortcut to the Windows Start Menu for “ESP-IDF 5.x CMD” — a pre-configured command prompt with all environment variables set

Installation takes 20–40 minutes depending on connection speed. When finished, open the “ESP-IDF 5.x CMD” shortcut. You should see a command prompt with the IDF environment active. Type idf.py --version — it should print the installed version.

Installing on macOS

On macOS, the recommended approach uses the Homebrew package manager for system dependencies and the IDF install script for the Python environment and toolchain.

Install Homebrew if you do not have it: /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

Then install prerequisites:

brew install cmake ninja dfu-util python3 git

Clone the ESP-IDF repository. Choose a permanent location — the path in your IDF_PATH environment variable must remain stable:

mkdir -p ~/esp
cd ~/esp
git clone -b v5.2 --recursive https://github.com/espressif/esp-idf.git

The --recursive flag clones all submodules (additional libraries the IDF depends on). This adds several gigabytes of data. Run the install script:

cd ~/esp/esp-idf
./install.sh esp32

The script installs the Xtensa toolchain into ~/.espressif/ and sets up a Python virtual environment. When it finishes, add these lines to your ~/.zshrc or ~/.bashrc for convenient activation:

alias get_idf='. $HOME/esp/esp-idf/export.sh'

Open a new terminal and run get_idf to activate the IDF environment. Then idf.py --version to confirm.

Installing on Linux (Ubuntu/Debian)

Install system dependencies:

sudo apt-get update
sudo apt-get install git wget flex bison gperf python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0
sudo usermod -aG dialout $USER   # add yourself to dialout group for serial port access

Log out and back in after the usermod command. Then clone and install:

mkdir -p ~/esp && cd ~/esp
git clone -b v5.2 --recursive https://github.com/espressif/esp-idf.git
cd ~/esp/esp-idf
./install.sh esp32

Add the activation alias to ~/.bashrc:

echo "alias get_idf='. $HOME/esp/esp-idf/export.sh'" >> ~/.bashrc
source ~/.bashrc

Building Your First IDF Project: Hello World

ESP-IDF includes a “Hello World” example that prints to UART0 and restarts every 10 seconds. It is the minimal sanity check for your IDF environment.

Activate the environment (run get_idf on macOS/Linux, open the ESP-IDF CMD on Windows). Copy the example to a working directory:

cd ~/esp
cp -r $IDF_PATH/examples/get-started/hello_world .
cd hello_world

Configure the target chip:

idf.py set-target esp32

This generates the sdkconfig file with default settings for the ESP32. For the hello_world example, no additional configuration is needed. Build:

idf.py build

The first build compiles the entire ESP-IDF framework from source — this takes 5–15 minutes depending on your hardware. Subsequent builds are incremental and take only seconds. When done, build/hello_world.bin is your firmware.

Flash to your board (replace PORT with your actual port — COM3 on Windows, /dev/ttyUSB0 on Linux, /dev/cu.usbserial on macOS):

idf.py -p PORT flash

Open the serial monitor immediately after flash:

idf.py -p PORT monitor

You should see:

Hello world!
This is esp32 chip with 2 CPU core(s), WiFi/BT/BLE, silicon revision 1, 4MB external-flash
Minimum free heap size: 298252 bytes
Restarting in 10 seconds...

Press Ctrl+] to exit the monitor. Your IDF environment is confirmed working.

Project Structure

Understanding the IDF project layout helps you work with it confidently:

hello_world/
├── CMakeLists.txt       ← top-level build definition
├── sdkconfig            ← generated config (do not edit manually)
├── main/
│   ├── CMakeLists.txt   ← registers main component
│   └── hello_world_main.c ← your application code
├── components/          ← optional custom components
└── build/               ← compiled output (git-ignore this)

The main/ directory is the primary component. All your application source files go there. Additional reusable modules go in components/ subdirectories. The IDF build system (CMake + Ninja) discovers and compiles everything automatically.

menuconfig — Configuring Your Project

Run idf.py menuconfig to open the text-based configuration interface. Use arrow keys to navigate, Enter to select, Space to toggle options, and ? for help on any item. Key sections:

  • Component config → ESP32-specific → CPU frequency — 80, 160, or 240 MHz
  • Component config → Wi-Fi → Wi-Fi Task Core ID — pin Wi-Fi to Core 0
  • Partition Table — select a pre-defined partition table or custom CSV
  • Component config → Log output → Default log verbosity — reduce to WARN for production builds

Press S to save and Q to quit. Settings are written to sdkconfig and automatically included in the next build as preprocessor macros.

Key IDF Differences from Arduino

If you come from Arduino, a few IDF patterns are immediately different. There is no setup() and loop() — your program entry point is app_main() in C or C++. You create FreeRTOS tasks with xTaskCreate() or xTaskCreatePinnedToCore() rather than placing code in loop(). Memory allocation uses standard malloc()/free() (heap_caps_malloc for specific memory types). GPIO uses the gpio_set_direction/gpio_set_level API from driver/gpio.h instead of pinMode/digitalWrite.

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"

#define LED_PIN GPIO_NUM_2

void blink_task(void *arg) {
    gpio_set_direction(LED_PIN, GPIO_MODE_OUTPUT);
    while (1) {
        gpio_set_level(LED_PIN, 1);
        vTaskDelay(pdMS_TO_TICKS(500));
        gpio_set_level(LED_PIN, 0);
        vTaskDelay(pdMS_TO_TICKS(500));
    }
}

void app_main(void) {
    xTaskCreate(blink_task, "blink", 2048, NULL, 5, NULL);
}

This IDF blink example creates a FreeRTOS task that toggles GPIO 2 every 500 ms. vTaskDelay(pdMS_TO_TICKS(500)) suspends the task for 500 ms without burning CPU cycles — the scheduler runs other tasks during the delay.

Updating ESP-IDF

To update to a new version, navigate to your ESP-IDF directory, fetch the new tag, and re-run the install script:

cd ~/esp/esp-idf
git fetch --all --tags
git checkout v5.3
git submodule update --init --recursive
./install.sh esp32

Then run idf.py set-target esp32 in your existing projects to regenerate sdkconfig for the new version. Some version upgrades require manual sdkconfig changes — check the migration guide in the IDF release notes.

Frequently Asked Questions

Projects to Build

Put this knowledge to work — try one of these hands-on projects.