Simple to use, modern design - Install updates on your ESP32 over WiFi inside the browser with easy rollback feature
or donate Bitcoin for PrettyOTA
- Features
- Supported MCUs
- Demo
- Minimal example
- Installation
- Usage
- Documentation of all functions
- Use PrettyOTA with ESP-IDF
- Is Ethernet supported?
- Help I got compilation errors
- Usage in commercial applications and white labeling
- Donation
See also: License
You can view the changelog here: Changelog
- Easy to use (two lines of code)
- Drag and drop firmware or filesystem
.bin
file to start updating - Rollback to previous firmware with a button click
- Reboot remotely with a button click
- Show info about installed firmware (Firmware version, SDK version, build time)
- Automatic reboot after update/rollback can be enabled
- Support for authentication (login with username and password) using server generated keys
- Asynchronous web server
- Support for ArduinoOTA to directly upload firmware over WiFi inside Arduino IDE and PlatformIO (tutorial included)
- Optional firmware pulling from server: Configure PrettyOTA to automatically check a server for a new firmware version and install it. This is useful for many devices which all need updates at the same time. No manual firmware upload required anymore
- Show/hide specific board infos
- Disable/enable filesystem updates in code
- ESP32 (all variants)
- RP2040 and RP2350 (RaspberryPi Pico W) - coming soon!
- ESP8266 - Support is planned but is not the highest priority now since the ESP8266 is end of life
With the example code below you can access PrettyOTA at http://IP_ADDRESS/update
, or upload via OTA inside ArduinoIDE and PlatformIO
Replace IP_ADDRESS
in the URL with the IP address of your ESP32.
#include <Arduino.h>
#include <WiFi.h>
#include <PrettyOTA.h>
const char* WIFI_SSID = "YOUR_SSID";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
AsyncWebServer server(80); // Server on port 80 (HTTP)
PrettyOTA OTAUpdates;
void setup()
{
Serial.begin(115200);
// Initialize WiFi
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
// Wait for successful WiFi connection
while (WiFi.waitForConnectResult() != WL_CONNECTED)
{
Serial.println("[WiFi] Connection failed! Rebooting...");
delay(3000);
ESP.restart();
}
// Print IP address
Serial.println("PrettyOTA can be accessed at: http://" + WiFi.localIP().toString() + "/update");
// Initialize PrettyOTA
OTAUpdates.Begin(&server);
// Set unique Hardware-ID for your hardware/board
OTAUpdates.SetHardwareID("UniqueBoard1");
// Set firmware version to 1.0.0
OTAUpdates.SetAppVersion("1.0.0");
// Set current build time and date
PRETTY_OTA_SET_CURRENT_BUILD_TIME_AND_DATE();
// Start web server
server.begin();
}
void loop()
{
// Give CPU time to other running tasks
delay(100);
}
To use this library with PlatformIO, simply search for PrettyOTA inside PlatformIO Library Manager.
platformio.ini
:
lib_compat_mode = strict
You can download PrettyOTA from the Arduino Library Manager. Simply search for PrettyOTA inside ArduinoIDE.
Alternatively you can download the latest release / .zip
file from GitHub and import it into the ArduinoIDE (Sketch->Include library->Add .ZIP library).
Download the latest release / .zip
file from releases.
You don't have to install the dependencies manually when using ArduinoIDE or PlatformIO. Simply search for PrettyOTA in the library manager and install it.
PrettyOTA needs the following libraries:
To enable authentication with username and password, simply pass the username and password to the Begin(...)
function:
OTAUpdates.Begin(&server, "username", "password");
You can always change the username or password after PrettyOTA has been initialized using:
void SetAuthenticationDetails(const char* const username, const char* const password, bool passwordIsMD5Hash = false);
To disable authentication after it has been enabled previously, pass empty values for username
and password
to SetAuthenticationDetails()
:
// This will disable authentication
SetAuthenticationDetails("", "");
It is also possible to only have an username but no password (and vice versa). Simply leave one of the parameters (username or password) empty.
Authentication is disabled by default if you don't pass any values to username
and password
inside Begin()
.
It's better not to store the password as clear text. PrettyOTA supports setting the password as a MD5 hash.
Simply pass the hash string to Begin(...)
as the password argument and set passwordIsMD5Hash
to true
:
// The password is "123" (without quotes)
// The MD5 hash of "123" is "202cb962ac59075b964b07152d234b70"
OTAUpdates.Begin(&server, "username", "202cb962ac59075b964b07152d234b70", true);
If you don't want to use the web interface of PrettyOTA, you can directly upload the firmware via OTA using PlatformIO or ArduinoIDE.
For ArduinoIDE you don't have to change anything. The ESP32 will show up under boards as an WiFi OTA target.
For PlatformIO you have to change the platformio.ini
file like usual for OTA uploads and add the following:
upload_protocol = espota
upload_port = 192.168.x.x
Replace the IP address with the IP address of your ESP32.
The Hardware ID should be a unique identifier for your hardware/board. You can set it using:
void SetHardwareID(const char* const hardwareID);
If you don't set a Hardware ID, the default "MyBoard1"
will be used. It is not recommended to leave the Hardware ID unchanged, as every board would get the same default value.
You can manually set the version number and build time/date (must be done when using ArduinoIDE or PlatformIO without ESP-IDF framework). If you use PlatformIO with the ESP-IDF framework (you can use both, ESP-IDF and the Arduino Framework at the same time), PrettyOTA can automatically detect the version number and build time/date for you.
When using the ArduinoIDE you must manually set the version number and build time/date.
To set the firmware version inside ArduinoIDE (or setting it manually if you use PlatformIO) use the following function inside setup()
:
void SetAppVersion(const char* const appVersion);
To set the current build time and date you can use the macro PRETTY_OTA_SET_CURRENT_BUILD_TIME_AND_DATE();
inside `setup().
The macro will call SetAppBuildTimeAndDate(const char* const appBuildTime, const char* const appBuildDate)
with the current build time and date already filled in for you.
If you want to set a specific build time or date, just call SetAppBuildTimeAndDate(...)
manually inside setup()
:
OTAUpdates.SetAppBuildTimeAndDate("17:10:00", "Mar 31 2025");
You don't have to follow any specific formatting for the time or date. Both parameters are strings and can be set to any value you like.
void setup()
{
// ...
// Initialize PrettyOTA
OTAUpdates.Begin(&server);
// Set hardware id
OTAUpdates.SetHardwareID("MyUniqueBoard");
// Set firmware version to 1.0.0
OTAUpdates.SetAppVersion("1.0.0");
// Set current build time and date
PRETTY_OTA_SET_CURRENT_BUILD_TIME_AND_DATE();
// Or manually set a specific build time and date
//OTAUpdates.SetAppBuildTimeAndDate("17:10:00", "Mar 31 2025");
// ...
}
In PlatformIO simply create a text file called version.txt
in your projects root folder. The content of the textfile must be one line containing the firmware version:
1.0.0
PlatformIO will automatically find the version.txt
file and set the version of your firmware, which gets displayed by PrettyOTA in the browser.
See Use PrettyOTA with ESP-IDF below for details and examples.
The build time and date will be automatically set. You don't have to do anything. PrettyOTA reads the time and date of the build from the firmware partition using esp_ota_get_app_description()
.
You can set it manually too with:
OTAUpdates.SetAppBuildTimeAndDate("17:10:00", "Mar 31 2025");
You can use mDNS to display the hostname for the OTA upload target inside ArduinoIDE. You can also use it to access the ESP32 using a normal URL like http://myesp.local/update
in your local network instead of the IP address.
void setup()
{
// ...
// Setup mDNS
// You must call "MDNS.begin()" BEFORE "OTAUpdates.Begin()"
MDNS.begin("myesp"); // http://myesp.local/
// Initialize PrettyOTA
OTAUpdates.Begin(&server);
// ...
}
The ArduinoIDE will now show the ESP32 as "myesp" like specified in the code above:
For a full example see mDNS example.
You can define your own callbacks which get called by PrettyOTA during the update.
Example:
#include <Arduino.h>
#include <WiFi.h>
#include <PrettyOTA.h>
const char* WIFI_SSID = "YOUR_SSID";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
AsyncWebServer server(80); // Server on port 80 (HTTP)
PrettyOTA OTAUpdates;
// Gets called when update starts
// updateMode can be FILESYSTEM or FIRMWARE
void OnOTAStart(NSPrettyOTA::UPDATE_MODE updateMode)
{
Serial.println("OTA update started");
if(updateMode == NSPrettyOTA::UPDATE_MODE::FIRMWARE)
Serial.println("Mode: Firmware");
else if(updateMode == NSPrettyOTA::UPDATE_MODE::FILESYSTEM)
Serial.println("Mode: Filesystem");
}
// Gets called while update is running
// currentSize: Number of bytes already processed
// totalSize: Total size of new firmware in bytes
void OnOTAProgress(uint32_t currentSize, uint32_t totalSize)
{
Serial.printf("OTA Progress Current: %u bytes, Total: %u bytes\n", currentSize, totalSize);
}
// Gets called when update finishes
void OnOTAEnd(bool successful)
{
if (successful)
Serial.println("OTA update finished successfully");
else
Serial.println("OTA update failed");
}
void setup()
{
Serial.begin(115200);
// Initialize WiFi
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
// Wait for successful WiFi connection
while (WiFi.waitForConnectResult() != WL_CONNECTED)
{
Serial.println("[WiFi] Connection failed! Rebooting...");
delay(3000);
ESP.restart();
}
// Print IP address
Serial.println("PrettyOTA can be accessed at: http://" + WiFi.localIP().toString() + "/update");
// Initialize PrettyOTA and set username and password for authentication
OTAUpdates.Begin(&server, "admin", "123");
// Set unique Hardware-ID for your hardware/board
OTAUpdates.SetHardwareID("UniqueBoard1");
// Set firmware version to 1.0.0
OTAUpdates.SetAppVersion("1.0.0");
// Set current build time and date
PRETTY_OTA_SET_CURRENT_BUILD_TIME_AND_DATE();
// Set custom callbacks
OTAUpdates.OnStart(OnOTAStart);
OTAUpdates.OnProgress(OnOTAProgress);
OTAUpdates.OnEnd(OnOTAEnd);
// Start web server
server.begin();
}
void loop()
{
// Give CPU time to other running tasks
delay(100);
}
For the full example see Callbacks example.
PrettyOTA provides built-in default callbacks, which just print the update status to the SerialMonitor (or any other Stream you specified with PrettyOTA::SetSerialOutputStream(Stream*)
).
To use the built-in default callbacks:
// Use built-in default callbacks.
// Do not call OnStart, OnProgress or OnEnd anymore, since they would override the built-in default callbacks
OTAUpdates.UseDefaultCallbacks();
The default callbacks also support color formatted printing. Every terminal and serial monitor supports that feature, except ArduinoIDE's serial monitor.
To enable color formatted printing, set the printWithColor
parameter to true
:
// Use built-in default callbacks with color formatted printing
OTAUpdates.UseDefaultCallbacks(true);
When using the default callbacks you will get the following output on your Serial-Monitor during an OTA update (colors are not supported by ArduinoIDE):
If you want to upload a filesystem image, the filesystem must be unmounted before the update begins (not necessary for firmware updates).
You can use a custom callback for OnStart(...)
, and unmount the filesystem (i.e. SPIFFS/LittleFS) inside the OnStart(...)
callback:
void CustomOnStart(NSPrettyOTA::UPDATE_MODE updateMode)
{
// Is the filesystem going to be updated?
if(updateMode == NSPrettyOTA::UPDATE_MODE::FILESYSTEM)
{
// Unmount SPIFFS filesystem here
SPIFFS.end();
}
}
void setup()
{
// ...
OTAUpdates.Begin(&server);
// Set callback
OTAUpdates.OnStart(CustomOnStart);
// ...
}
PrettyOTA uses these URLs by default:
- Customizable URLs:
/login
: Login page if authentication is enabled. Redirects to/update
if authentication is disabled/update
: PrettyOTA's main website. Redirects to/login
if authentication is enabled and client is not logged in
- Fixed URLs used internally (cannot be changed):
/prettyota/start
:/prettyota/upload
:/prettyota/doRollback
:/prettyota/queryInfo
:/prettyota/queryPrettyOTAInfo
:/prettyota/rebootCheck
:/prettyota/doManualReboot
:/prettyota/logout
:
If you want to change the /login
and / or /update
URLs, specify them in the Begin()
function:
// Use custom URLs and enable authentication (username and password)
OTAUpdates.Begin(&server, "admin", "123", false, "/myCustomUpdateURL", "/myCustomLoginURL");
With the code above you can reach PrettyOTA under http://YOUR_IP/myCustomUpdateURL
.
Inside the ArduinoIDE make sure you select a partition scheme with OTA. For example you can use Minimal SPIFFS (APP with OTA)
. However having a separate SPIFFS is not a requirement.
To be able to use OTA updates you need (at least) two app partitions (for the firmware) and one ota_data partition (for configuration).
You also need a NVS partition where PrettyOTA can store the logged in clients. If you don't have a NVS partition, logged in clients won't be remembered after reboot/update and PrettyOTA will print an error message (but everything else will work without issues).
SPIFFS is not a requirement.
Example partitions.csv
(4MB flash) for PlatformIO including an optional SPIFFS partition:
# Name, Type, SubType, Offset, Size, Flags
nvs,data,nvs,0x9000,0x13000,
phy,data,phy,0x1C000,0x2000,
otadata,data,ota,0x1E000,0x2000,
app0,app,ota_0,0x20000,0x1D0000,
app1,app,ota_1,0x1F0000,0x1D0000,
spiff,data,spiffs,0x3C0000,0x40000,
I can recommend the ESP Partition Builder Website for an easy way to manage partitions.
PrettyOTA automatically saves logged in clients (sessionIDs) to the NVS partition and loads them during initialization. This enables clients to stay logged in after a reboot or firmware update of the ESP32.
For this to work you must have a NVS partition (for example inside the partitions.csv
file for PlatformIO).
Without a NVS partition saving will not work and you have to log in again after a reboot or firmware update of the ESP32.
// Returns true on success, false on error or if already initialized
bool Begin(AsyncWebServer* const server, // The AsyncWebServer instance
const char* const username = "", // (Optional) Username for authentication
const char* const password = "", // (Optional) Password for authentication
bool passwordIsMD5Hash = false, // (Optional) Is the password cleartext or a MD5 hash?
const char* const mainURL = "/update", // (Optional) Main URL for PrettyOTA
const char* const loginURL = "/login", // (Optional) Login page URL
uint16_t OTAport = 3232); // (Optional) The port for OTA uploads inside ArduinoIDE/PlatformIO.
// Leave it set to `3232` for compatibility with ArduinoIDE OTA upload
void SetAuthenticationDetails(const char* const username, // Username
const char* const password, // Password
bool passwordIsMD5Hash = false); // (Optional) Is the password cleartext or a MD5 hash?
If username
and password
is empty, authentication will be disabled. The /login
page then redirects automatically to the main /update
page.
See Authentication below for details and examples.
Call this function to use the built-in default callbacks. The default callbacks only print the update status to the Serial-Monitor (or any other Stream you specified).
See Use default callbacks for more details.
void UseDefaultCallbacks(bool printWithColor = false);
PrettyOTA outputs log messages when an error occurs. You can specify where these log messages should be printed. The default is printing to Serial
(you must have Serial.begin(115200);
inside setup()
for this to work).
If you want to print to a different output, use the following function:
// Set the Stream to write log messages too (Example: Use &Serial as argument)
void SetSerialOutputStream(Stream* const serialStream);
Example:
// Print to Serial1 instead of default Serial
OTAUpdates.SetSerialOutputStream(&Serial1);
Set custom user defined callbacks. See callbacks example.
// Set custom callback functions
void OnStart(std::function<void(NSPrettyOTA::UPDATE_MODE updateMode)> func);
void OnProgress(std::function<void(uint32_t currentSize, uint32_t totalSize)> func);
void OnEnd(std::function<void(bool successful)> func);
See Set HardwareID.
void SetHardwareID(const char* const hardwareID);
See Set firmware version number, build time and date.
static void SetAppVersion(const char* const appVersion);
See Set firmware version number, build time and date.
static void SetAppBuildTimeAndDate(const char* const appBuildTime, const char* const appBuildDate);
See Set firmware version number, build time and date.
#define PRETTY_OTA_SET_CURRENT_BUILD_TIME_AND_DATE() PrettyOTA::SetAppBuildTimeAndDate(__TIME__, __DATE__)
PrettyOTA relies on ESPAsyncWebServer which is an Arduino library. However you can include the Arduino dependencies as a package inside your ESP-IDF project. No changes are required to your code base and the Arduino stuff is not interfering with anything in the background.
Instructions on how to use ESP-IDF and Arduino framework at the same time will follow soon.
Yes, Ethernet is supported with PrettyOTA and AsyncWebServer. You don't have to change anything regarding PrettyOTA's usage.
If you get the following error when compiling with PlatformIO:
'ip_addr_t' {aka 'struct ip_addr'} has no member named 'addr'; did you mean 'u_addr'?
You must add this line to your platformio.ini
:
lib_compat_mode = strict
You are allowed to use PrettyOTA for commercial purposes. An acknowledgement is required. However you are not allowed to modify the source code and then claim that you wrote it.
It is also not permitted to change the name or logo of PrettyOTA, even when redistributing changed source code versions. If you want to change the logo and/or name, you must acquire a commercial license.
A commercial license also includes technical support for companies, who need reliable and fast solutions.
See also: License
If you want to white-label PrettyOTA (use a custom name and logo for commercial or private application), please contact me via E-Mail for inquiries. White-labeling is the only use case which is not free.
❤️ If you want to help out a student with paying rent, a donation for PrettyOTA and my work would be greatly appreciated!
💬 If you don't want to donate, please support by telling other people about PrettyOTA!

Wallet address (Bitcoin):
32nkLAWGsAtn3SNo1vb7RSQKLP93YJCwke