Every ESP32 tutorial starts with const char* ssid = "MyWiFi";. That is fine on your desk and a disaster as soon as the device leaves it. Give the board to a friend, move house, change your router password or build ten units for different customers, and every one needs the firmware edited and re-flashed over USB.
WiFiManager fixes this. On first boot, or whenever it cannot join a saved network, the ESP32 starts its own access point. You connect to it from a phone, a setup page opens automatically (a captive portal), you pick your home network from a list, type the password, and the ESP32 saves it and joins. Next boot it reconnects on its own. The same portal can also collect other settings, such as an MQTT server address or a device name, so one firmware image works for everybody.
This guide sets it up end to end: basic use, a timeout and fallback, custom fields saved to flash, a button to reopen the portal, a non-blocking mode, a static IP and the problems people actually hit.
How WiFiManager Works
- On boot, the library checks the credentials stored in the ESP32's flash memory.
- If it can connect, your program continues normally.
- If there are no credentials or the connection fails, it starts an access point (for example
ESP32-Setup) and a tiny web server with DNS redirection. - Your phone joins that access point and is sent to the portal page, usually automatically. If not, you open
192.168.4.1. - You choose a network, enter the password and press save. The ESP32 stores the credentials, restarts the WiFi stack and joins your network.
The credentials are saved by the ESP32 WiFi system in non-volatile storage, so they survive power loss. This is the same access point mode described in the WiFi tutorial, wrapped in a polished setup flow.
Install the Library
In the Arduino IDE open Sketch → Include Library → Manage Libraries, search for WiFiManager, and install the one by tzapu (version 2.x supports the ESP32). If the installed version is a very old 0.x release, update it, because the ESP32 support lives in the newer one.
The Simplest Possible Use
#include <WiFi.h>
#include <WiFiManager.h>
void setup() {
Serial.begin(115200);
WiFiManager wm;
bool connected = wm.autoConnect("ESP32-Setup", "setup1234");
if (!connected) {
Serial.println("Failed to connect, restarting");
ESP.restart();
}
Serial.println("WiFi connected");
Serial.print("IP: ");
Serial.println(WiFi.localIP());
}
void loop() {
}
autoConnect(name, password) does everything. The first argument is the access point name you will see on your phone, and the second is its password (at least 8 characters). Leave the password out to create an open access point, which is easier but means anyone nearby can open your setup page, so a password is better.
First run: open WiFi settings on your phone, join ESP32-Setup, and wait for the portal to appear. If it does not pop up on its own, browse to http://192.168.4.1. Tap Configure WiFi, select your network, enter the password and save. The Serial Monitor shows the connection and the new IP address.
Second run: reset the board. It reconnects with the saved details and never opens the portal.
Note that autoConnect() blocks until it connects or gives up, which brings us to timeouts.
Timeouts and Fallback
By default the portal stays open forever. That is bad for a device that should keep working after a power cut while the router is still booting: it would wait in setup mode instead of retrying. Set timeouts:
WiFiManager wm;
wm.setConnectTimeout(20); // seconds to try a saved network
wm.setConfigPortalTimeout(180); // seconds the portal stays open
if (!wm.autoConnect("ESP32-Setup", "setup1234")) {
Serial.println("No WiFi. Restarting to try again...");
delay(3000);
ESP.restart();
}
With these settings the ESP32 tries the saved network for 20 seconds, then opens the portal for three minutes, and if nobody configures it, restarts and tries the saved network again. This loop is exactly what you want after a power outage: the ESP32 boots faster than the router, fails once, restarts and connects when the router is ready.
If your project can do something useful offline (log to an SD card, run a local display), continue running instead of restarting, and retry WiFi later in the background.
Know when the portal opens
A callback lets you show a message on a display or light an LED:
void onConfigMode(WiFiManager* wm) {
Serial.println("Entered config mode");
Serial.print("Connect to: ");
Serial.println(wm->getConfigPortalSSID());
Serial.print("Portal IP: ");
Serial.println(WiFi.softAPIP());
}
wm.setAPCallback(onConfigMode);
Custom Fields: Collect MQTT Settings and More
The portal can ask for extra values. This is how you build one firmware that works with any broker address.
#include <WiFi.h>
#include <WiFiManager.h>
#include <Preferences.h>
Preferences prefs;
bool shouldSave = false;
char mqttServer[40] = "192.168.1.10";
char mqttPort[6] = "1883";
char deviceName[24] = "esp32-node";
void saveConfigCallback() {
shouldSave = true;
}
void setup() {
Serial.begin(115200);
// load saved values
prefs.begin("config", false);
strlcpy(mqttServer, prefs.getString("server", mqttServer).c_str(), sizeof(mqttServer));
strlcpy(mqttPort, prefs.getString("port", mqttPort).c_str(), sizeof(mqttPort));
strlcpy(deviceName, prefs.getString("name", deviceName).c_str(), sizeof(deviceName));
WiFiManagerParameter pServer("server", "MQTT server", mqttServer, 40);
WiFiManagerParameter pPort("port", "MQTT port", mqttPort, 6);
WiFiManagerParameter pName("name", "Device name", deviceName, 24);
WiFiManager wm;
wm.setSaveConfigCallback(saveConfigCallback);
wm.addParameter(&pServer);
wm.addParameter(&pPort);
wm.addParameter(&pName);
wm.setConfigPortalTimeout(180);
if (!wm.autoConnect("ESP32-Setup", "setup1234")) {
ESP.restart();
}
strlcpy(mqttServer, pServer.getValue(), sizeof(mqttServer));
strlcpy(mqttPort, pPort.getValue(), sizeof(mqttPort));
strlcpy(deviceName, pName.getValue(), sizeof(deviceName));
if (shouldSave) {
prefs.putString("server", mqttServer);
prefs.putString("port", mqttPort);
prefs.putString("name", deviceName);
Serial.println("Config saved");
}
Serial.printf("Server: %s:%s Name: %s\n", mqttServer, mqttPort, deviceName);
}
void loop() {
}
How it works: the three WiFiManagerParameter objects become extra text boxes on the portal page, pre-filled with the current values. When you save, the callback sets a flag, and after autoConnect() returns, the sketch copies the new values out of the parameters and writes them to flash with Preferences. On the next boot, they are read back first and used as the defaults.
Use Preferences (or LittleFS) rather than EEPROM for this: it is simpler and designed for key-value settings. The stored values connect directly to projects like the MQTT temperature sensor and the smart switch, where the broker address no longer needs to be compiled in.
A Button to Reopen the Portal
If someone changes their router or moves the device, they need a way to re-enter setup without re-flashing. The BOOT button (GPIO 0) is handy: hold it for three seconds to open the portal on demand.
#include <WiFi.h>
#include <WiFiManager.h>
const int RESET_BUTTON = 0; // BOOT button, LOW when pressed
unsigned long pressStart = 0;
void startPortal() {
WiFiManager wm;
wm.setConfigPortalTimeout(180);
if (!wm.startConfigPortal("ESP32-Setup", "setup1234")) {
Serial.println("Portal timed out");
}
}
void checkResetButton() {
if (digitalRead(RESET_BUTTON) == LOW) {
if (pressStart == 0) pressStart = millis();
if (millis() - pressStart > 3000) {
Serial.println("Button held: opening config portal");
startPortal();
pressStart = 0;
}
} else {
pressStart = 0;
}
}
void setup() {
Serial.begin(115200);
pinMode(RESET_BUTTON, INPUT_PULLUP);
WiFiManager wm;
wm.autoConnect("ESP32-Setup", "setup1234");
}
void loop() {
checkResetButton();
// your normal code
}
To wipe saved credentials completely (for example before giving the board away), call wm.resetSettings(); once. You can wire that to a longer button hold, such as ten seconds.
On your own boards, use a separate button on a safe pin such as GPIO 27 instead of GPIO 0, so a stuck bootloader button can never trigger it by accident (see the ESP32 pinout guide).
Non-Blocking Mode
autoConnect() freezes your program while the portal is open. If the device should keep doing its job (reading sensors, driving outputs) while waiting to be configured, use non-blocking mode:
WiFiManager wm;
void setup() {
Serial.begin(115200);
wm.setConfigPortalBlocking(false);
wm.setConfigPortalTimeout(0); // keep portal available, never time out
if (wm.autoConnect("ESP32-Setup", "setup1234")) {
Serial.println("Connected");
} else {
Serial.println("Portal running in background");
}
}
void loop() {
wm.process(); // must be called often to serve the portal
// your normal code keeps running here
}
process() serves the portal when it is open and does nothing otherwise, so it is cheap to leave in the loop.
Static IP Through the Portal
Combine WiFiManager with a fixed address using setSTAStaticIPConfig, before autoConnect():
wm.setSTAStaticIPConfig(IPAddress(192,168,1,184),
IPAddress(192,168,1,1),
IPAddress(255,255,255,0));
This hardcodes the network layout again, which defeats part of the purpose, so on distributed devices a router-side DHCP reservation is usually better. The static IP guide explains the trade-off in detail.
Security Notes
- Put a password on the setup access point. An open portal lets anyone in range read the setup page and replace your WiFi credentials.
- Set a portal timeout. A portal that closes after a few minutes is far less exposed than one that stays up forever.
- The saved WiFi password is stored in flash in plain form by the ESP32 stack. Anyone with physical access and a USB cable can read it. That is true of hardcoded credentials too.
- Do not reuse a valuable WiFi password on a device you hand to other people. Give the device a guest network.
Troubleshooting
The portal does not open automatically on the phone. Some phones need a moment, or the "sign in to network" prompt is hidden. Open a browser and go to http://192.168.4.1. Android may also disconnect from a network that has no internet. If so, tap to stay connected.
The setup network does not appear. Wait a few seconds after boot. Check that the board really entered config mode (look at the Serial Monitor). Make sure the phone is searching 2.4 GHz (the ESP32 access point is 2.4 GHz only).
My network is missing from the list. The ESP32 cannot see 5 GHz networks. A router that broadcasts only 5 GHz will never appear. Move the ESP32 closer and refresh. Hidden networks must be typed manually.
It keeps going back into setup mode. The saved password is wrong, the signal is weak or the router is slow. Raise setConnectTimeout(), re-enter the credentials and check the signal with the WiFi tutorial. If it always happens after a power cut, the router boots slower than the ESP32, and the restart loop from the timeout section solves it.
Compile error: WiFiManager.h not found or WebServer conflicts. Install tzapu's WiFiManager, not an ESP8266-only fork. Remove duplicate similarly named libraries from your libraries folder.
Custom parameter values come back empty. Read them after autoConnect() returns using getValue(), and only save when the save callback fired.
The new credentials do not stick after reflashing. Erasing flash completely (for example by selecting "Erase All Flash" when uploading) removes saved credentials. Choose "Sketch only" if you want to keep them.
For deeper connection issues, use the WiFi troubleshooting guide.
Alternatives and Frequently Asked Questions
Are there other ways to provision WiFi? Yes. Espressif's BLE provisioning and SoftAP provisioning with a phone app, and Improv WiFi (used by ESPHome) let you send credentials over Bluetooth or USB. ESPHome includes a fallback hotspot and captive portal out of the box, see Home Assistant without MQTT.
Does WiFiManager work on ESP8266 too? Yes, it started there. The same code mostly works on both, which is useful if you also build with NodeMCU boards.
Can I theme the portal? Yes. You can change the title, add a custom menu and inject your own CSS. Check the library examples.
Does it slow down boot? Only when it has to open the portal. A normal boot with saved credentials connects as fast as plain WiFi.begin().
Is it suitable for products I sell? It is a good starting point for hobby and small-batch devices. For commercial products, consider a provisioning scheme with a secure per-device setup password and a proper factory-reset procedure.
What should I build with it? Anything that leaves your desk: the MQTT sensor, the smart switch, or an ESP32-CAM streamer.