Saving settings on an ESP32 with Preferences
Or see every lesson.
- 455parts on the bench
- 43boards running now
- 1.00xreal time, on every board
The Preferences library is how an ESP32 sketch remembers a number or a string after the power goes off: a WiFi password, a calibration value, a count. It ships with the ESP32 core, so there is nothing to install. You open a namespace with begin(), store and read values by key with put and get calls of the right type, and close it with end(). Underneath, the values live in NVS, a small key-value store in the flash.
The circuit above is an ESP32 DevKit on its own; the serial monitor is the whole interface. Below is the classic boot counter, strings with defaults, the rules that make a put return 0, and how this compares with EEPROM.
A boot counter
This is the example most tutorials start with. begin("my-app", false) opens the namespace "my-app" for writing, creating it the first time. getUInt("counter", 0) reads the key, or 0 when it is not there yet. The sketch adds one, prints it, stores it with putUInt() and closes the namespace.
On a board, every reset and every power cycle prints the next number. Here the first run printed "Boot count: 1", and with ESP.restart() after each count it printed "Boot count: 2" and then "Boot count: 3", because a restart keeps the stored values. Mokxi’s ESP32 has no flash behind it, so Preferences live in RAM: they survive ESP.restart(), and a new Run or the EN button starts again from 1. The calls and their answers are the core’s; only the lifetime is shorter.
#include <Preferences.h>
Preferences prefs;
void setup() {
Serial.begin(115200);
prefs.begin("my-app", false);
unsigned int count = prefs.getUInt("counter", 0);
count++;
Serial.printf("Boot count: %u\n", count);
prefs.putUInt("counter", count);
prefs.end();
if (count < 3) {
delay(100);
ESP.restart();
}
}
void loop() {
}Strings, defaults and every other type
Each type has its own pair of calls: putInt/getInt, putUInt, putLong, putFloat, putBool, putString/getString and putBytes for a struct. Read a key with the same type you stored it with. A float stored with putFloat("t", 21.5) came back from getFloat("t", 0) as 21.50.
Every get takes a default, returned when the key is missing, so the first run of a sketch needs no special case. After putString("ssid", "HomeNet"), getString("ssid", "none") printed "HomeNet" and getString("pass", "none") printed "none". That is the pattern for WiFi settings: read them with a default in setup(), and only ask for new ones when you got the default back.
isKey("ssid") printed 1 while the key existed; after remove("ssid") it printed 0. clear() deletes every key in the open namespace and leaves the others alone.
#include <Preferences.h>
Preferences prefs;
void setup() {
Serial.begin(115200);
prefs.begin("wifi", false);
prefs.putString("ssid", "HomeNet");
Serial.println(prefs.getString("ssid", "none"));
Serial.println(prefs.getString("pass", "none"));
Serial.println(prefs.isKey("ssid"));
prefs.remove("ssid");
Serial.println(prefs.isKey("ssid"));
prefs.end();
}
void loop() {
}Saving a whole struct with putBytes
When a sketch has several settings, keeping them in one struct and saving it in one call is tidier than a key for each. putBytes(key, &settings, sizeof(settings)) stores the struct’s bytes as they sit in memory, and getBytes(key, &settings, sizeof(settings)) copies them back. Both return the number of bytes they moved. In the sketch below each returned 8, the size of an int and a float on an ESP32, and the struct came back with a brightness of 180 and an offset of -1.50.
Check getBytesLength(key) against sizeof before you trust what you read. If you add a field to the struct later, the stored copy from the old version is shorter, and reading it would leave the new field holding whatever was there. On the first run the key does not exist yet, getBytesLength() returns 0, and the sketch keeps the defaults it started with. Here it printed 0 before the first put and 8 after it.
#include <Preferences.h>
struct Settings {
int brightness;
float offset;
};
Preferences prefs;
Settings settings = {128, 0.0};
void setup() {
Serial.begin(115200);
prefs.begin("my-app", false);
Serial.println(prefs.getBytesLength("settings"));
Settings changed = {180, -1.5};
Serial.println(prefs.putBytes("settings", &changed, sizeof(changed)));
Serial.println(prefs.getBytesLength("settings"));
if (prefs.getBytesLength("settings") == sizeof(settings)) {
Serial.println(prefs.getBytes("settings", &settings, sizeof(settings)));
}
Serial.println(settings.brightness);
Serial.println(settings.offset);
prefs.end();
}
void loop() {
}When a put returns 0
Every put returns the number of bytes it stored, and 0 when it stored nothing. putUInt() returned 4 for a good key. Four mistakes make it return 0, and each one was made here on purpose:
A key longer than 15 characters. "key_with_15chrs" stored 4 bytes; "a_key_with_16chr", one character longer, returned 0. Namespaces have the same limit, and begin("a_namespace_too_long", false) returned 0, so every call after it failed too. A namespace opened read-only: after begin("wifi", true), putUInt("x", 1) returned 0 while gets still worked. And no begin() at all: putUInt("x", 1) returned 0 and getUInt("x", 7) gave back the default, 7.
Keys are per namespace. A key stored under "wifi" was not found from "other", which returned its default, 99. Two libraries can each use a key called "mode" without a clash, as long as they use different namespaces.
Preferences or EEPROM on an ESP32?
The ESP32 has no EEPROM chip. EEPROM.h on an ESP32 is emulated in a flash partition: you call EEPROM.begin(size) first and EEPROM.commit() after writing, or nothing is saved. Here EEPROM.write(0, 42), then commit(), returned 1, and read(0) gave 42. It is there for sketches ported from an Uno.
Preferences is the better choice for new code. You store by name rather than by address, each value keeps its type, and NVS spreads its writes over the partition’s pages, which matters because flash wears out after many erase cycles. Either way, write when a value changes, not on every pass of loop().
Questions
Where does the ESP32 Preferences library store data?
In NVS, a key-value store in a partition of the flash, so values survive resets and power loss. In Mokxi they are kept in RAM, which survives ESP.restart() but not a new Run.
Why does my Preferences put return 0?
The key or namespace is longer than 15 characters, begin() was not called or failed, or the namespace was opened read-only with begin(name, true).
How do I save a string with ESP32 Preferences?
prefs.putString("ssid", value) to store it and prefs.getString("ssid", "none") to read it back, with "none" returned when it was never stored.
Should I use Preferences or EEPROM on an ESP32?
Preferences. The ESP32 has no EEPROM, and EEPROM.h emulates one in flash and needs commit(). Preferences stores typed values by name.
What is the ESP32 preferences link for the Arduino IDE?
That is the boards manager URL that goes in the IDE’s own Preferences window: https://espressif.github.io/arduino-esp32/package_esp32_index.json, from Espressif’s installation guide.
Keep going
Where the facts on this page come from
Build this for real
Open the editor, change a value and watch the number move with it. Nothing to install, and no account needed.