Electronics Design AU
ESP32

How Do You Use ESP32 NVS (Non-Volatile Storage) for Settings and Key-Value Data?

Last updated 14 July 2026 · 6 min read

Direct Answer

NVS (Non-Volatile Storage) is ESP-IDF's built-in key-value storage library for persisting small amounts of data — Wi-Fi credentials, calibration values, device configuration, counters — across power cycles in a dedicated flash partition, with wear levelling handled automatically. Firmware opens a named namespace with nvs_open(), reads and writes typed values (integers, strings, or arbitrary binary blobs) with nvs_get_*()/nvs_set_*() functions, and calls nvs_commit() to make writes durable. It is the right tool for small, frequently-updated settings; it is the wrong tool for large files or bulk data, which belong in a filesystem like LittleFS or FatFs instead.

Detailed Explanation

Almost every ESP32 product needs to persist some data across power cycles and reboots — Wi-Fi credentials, a device's paired-peer list, calibration offsets, a boot counter, user configuration. ESP-IDF's NVS (Non-Volatile Storage) library is the standard, built-in solution for exactly this class of data: small, discrete, key-value pairs, stored in a dedicated flash partition with wear levelling and basic corruption resilience handled automatically, so application firmware doesn't need to implement flash erase-cycle management itself.

NVS is not a general filesystem. For larger data — files, logs, downloaded assets, anything with a natural directory structure — see embedded flash storage: littlefs vs FatFs for the filesystem-level alternative; the two are frequently used together in the same project for their respective strengths.

The NVS API

Using NVS follows a consistent pattern in every ESP-IDF project:

// Once at startup, before any other NVS call:
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
    ESP_ERROR_CHECK(nvs_flash_erase());
    err = nvs_flash_init();
}
ESP_ERROR_CHECK(err);

// Open a namespace (a named grouping of keys):
nvs_handle_t handle;
nvs_open("settings", NVS_READWRITE, &handle);

// Read and write typed values:
int32_t brightness = 50;
nvs_get_i32(handle, "brightness", &brightness);   // reads existing value, if present
nvs_set_i32(handle, "brightness", 80);             // writes a new value
nvs_commit(handle);                                // commits the write to flash

nvs_close(handle);

Namespaces group related keys under a single name (up to 15 characters), similar to a table or section — a project might use separate namespaces for wifi_creds, settings, and calibration so keys don't collide and related data is easy to manage together.

Typed getters/setters exist for each primitive type ESP-IDF supports: nvs_get_i8/nvs_set_i8 through i64/u64, nvs_get_str/nvs_set_str for null-terminated strings, and nvs_get_blob/nvs_set_blob for arbitrary binary data (a struct, for example) up to the practical size limits of the NVS partition. Each key (up to 15 characters) is scoped to its namespace and has exactly one type — attempting to read a key with the wrong type returns ESP_ERR_NVS_TYPE_MISMATCH rather than silently reinterpreting the stored bytes.

Committing writes. nvs_set_*() stages a write; nvs_commit() makes it durable. In practice, most applications call nvs_commit() immediately after a set (or a batch of related sets) rather than deferring it, since an uncommitted write is not guaranteed to survive an unexpected reset.

NVS Partition Sizing

NVS lives in a dedicated flash partition defined in the project's partitions.csv file, separate from the application and OTA partitions. Espressif's standard partition table presets allocate a modest default size for this partition — commonly in the 16KB–24KB range depending on which preset the project uses — sufficient for typical configuration data but not for anything resembling bulk storage. A project with unusually large NVS storage requirements (many device-specific calibration records, for example) can define a custom partition table with a larger nvs partition; the trade-off is less flash budget available for the application and OTA partitions on a given flash size.

Exceeding the partition's available space returns ESP_ERR_NVS_NOT_ENOUGH_SPACE from the write call — see the FAQ above for why checking this return value matters rather than assuming a write succeeded.

Wear Levelling and Reliability

NVS is built on a page-based storage scheme designed specifically for flash's erase-before-write constraint and finite erase-cycle endurance: writes are distributed across the partition's pages rather than always rewriting the same physical flash sector, and the library tracks page state to recover cleanly from a power loss mid-write rather than leaving the partition in a corrupted, unusable state. This is the main practical advantage of using NVS over a hand-rolled "write a struct to a fixed flash address" approach — the wear-levelling and crash-consistency logic is the part of a hand-rolled scheme that's easy to get wrong and expensive to debug in the field.

NVS Encryption for Production

For production devices storing sensitive data in NVS (Wi-Fi credentials, API keys, device secrets), ESP-IDF supports NVS encryption, which transparently encrypts NVS partition contents using a key managed through the same eFuse-based key infrastructure as ESP32 flash encryption. NVS encryption is commonly enabled alongside full flash encryption in a production security configuration — see that page for the eFuse key-management and Development-vs-Release-mode considerations that apply to both.

Design Considerations

  • Check the return value of every nvs_set_*() and nvs_commit() call in production code, not just during development — a full partition or a corrupted entry returns a specific error code rather than failing silently, and ignoring it can mean a setting that was never actually saved.
  • Use namespaces to organise related keys, rather than putting every key in a single default namespace — this becomes valuable as a project's configuration surface grows and makes bulk operations (like erasing only one namespace's data) straightforward.
  • Size the NVS partition to the application's actual data volume, not the default preset blindly — a project storing many discrete calibration records per install may need a larger custom partition table entry than the default provides.
  • Enable NVS encryption for any device storing credentials or secrets in flash, paired with the broader flash encryption decision covered in ESP32 Secure Boot and flash encryption — don't treat NVS as inherently secure storage without enabling this explicitly.
  • Zeus Design's embedded firmware team designs ESP32 configuration, provisioning, and secure storage architecture as part of ESP32 firmware development for production IoT products.

Common Mistakes

  • Calling NVS functions before nvs_flash_init() has run, producing ESP_ERR_NVS_NOT_INITIALIZED — see the FAQ above for the standard init-and-recover boilerplate that handles this correctly on first boot.
  • Not handling the ESP_ERR_NVS_NO_FREE_PAGES/ESP_ERR_NVS_NEW_VERSION_FOUND cases from nvs_flash_init(), leaving the device unable to boot after a partition table change or a factory-reset erase, instead of erasing and reinitialising the NVS partition automatically.
  • Using NVS for large or frequently-changing bulk data (logs, sensor buffers, downloaded files) instead of a proper filesystem partition — this both wastes the limited NVS partition space and defeats its wear-levelling design, which is tuned for small, infrequent updates, not continuous bulk writes.
  • Forgetting nvs_commit() after a set call, then losing the update on an unexpected reset because the write was staged but never made durable.
  • Storing secrets in NVS on a production device without enabling NVS/flash encryption, leaving credentials readable in plaintext to anyone with physical flash access — see ESP32 Secure Boot and flash encryption for how to close this gap.

Frequently Asked Questions

Why does nvs_open or nvs_get fail with ESP_ERR_NVS_NOT_INITIALIZED on first boot?
NVS must be explicitly initialised once at startup, before any other NVS call, by calling nvs_flash_init() — this is not automatic. A common first-boot failure mode is nvs_flash_init() itself returning ESP_ERR_NVS_NO_FREE_PAGES or ESP_ERR_NVS_NEW_VERSION_FOUND (typically after a partition table change or an erase), which the standard ESP-IDF pattern handles by calling nvs_flash_erase() followed by a retry of nvs_flash_init() — this is boilerplate present at the top of most ESP-IDF example projects specifically to handle this case rather than leaving the device unable to boot.
How much data can I actually store in NVS, and what happens if I exceed it?
NVS capacity is set by the size of the `nvs` partition defined in the project's partition table (partitions.csv), commonly 16KB–24KB by default depending on the partition scheme, though it can be sized larger if the project's partition table is customised. Exceeding available space returns ESP_ERR_NVS_NOT_ENOUGH_SPACE from the write call rather than silently failing or corrupting existing data — check the return value on every nvs_set_*() call rather than assuming it succeeded, particularly in code paths that write NVS during normal operation rather than only at first-boot provisioning.
Should I use NVS or a filesystem like LittleFS for my ESP32 project's persistent data?
Use NVS for small, discrete, frequently-updated key-value data — configuration values, calibration constants, small counters, Wi-Fi credentials — where the built-in wear levelling and simple typed API are the main benefits. Use a filesystem (LittleFS, FatFs — see embedded flash storage and filesystems for the general comparison) for larger data, files with a natural file/directory structure, or data written in large sequential blocks (logs, sensor data buffers, downloaded assets). Mixing both is common and normal: NVS for the device's own settings, a filesystem partition for bulk application data.

References

Related Questions

Related Forum Discussions