# How to Serialize and Persist UI State with ImGuiStorage in Dear ImGui

> Learn to serialize and persist UI state with ImGuiStorage in Dear ImGui. Manually save and load ImGuiID keys and values to disk for seamless user experience.

- Repository: [omar/imgui](https://github.com/ocornut/imgui)
- Tags: how-to-guide
- Published: 2026-07-15

---

**ImGuiStorage is a per-window key-value container that stores int, float, and void* values in a sorted vector, but because it lacks built-in persistence, you must manually iterate the Data vector, serialize the ImGuiID keys and values to disk, and reload them via SetInt(), SetFloat(), or SetVoidPtr() before the window is rendered.**

Dear ImGui (ocornut/imgui) provides the **ImGuiStorage** class as a lightweight state container attached to each window. While it survives across frames for the duration of the application session, it does not automatically write to disk. To persist UI state between application runs, you must implement custom serialization logic that reads from and writes to the `Data` vector defined in [`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h).

## Understanding ImGuiStorage Structure and Limitations

### The Underlying Data Layout

ImGuiStorage is implemented as a sorted vector of `ImGuiStoragePair` structs. According to the source in [`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h)【/imgui.h#L2794-L2815】, each pair contains an `ImGuiID key` and a union holding either `int val_i`, `float val_f`, or `void* val_p`. The container exposes public access to this vector via `ImGuiStorage::Data`, which is an `ImVector<ImGuiStoragePair>`.

```cpp
// From imgui.h - ImGuiStorage definition
struct ImGuiStorage
{
    ImVector<ImGuiStoragePair> Data;
    // ...
    void SetInt(ImGuiID key, int val);
    int GetInt(ImGuiID key, int default_val = 0) const;
    // ... float and void* variants
};

```

Because the storage uses a binary search algorithm to locate keys, maintaining the sorted order is critical when manually manipulating the Data vector.

### Why ImGuiStorage Does Not Persist Automatically

The storage object lives inside `ImGuiWindow` and is accessed via `ImGui::GetStateStorage()` (declared in [`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h)【/imgui.h#L1047-1048】). Dear ImGui explicitly avoids file I/O dependencies in its core library. Therefore, the `ImGuiStorage` API provides no serialization hooks, and the `Data` vector is cleared when the window is destroyed or the application exits. Persistence requires external code to iterate `storage->Data`, extract the key-value pairs, and write them to a file format of your choice (binary, JSON, or INI).

## How to Serialize ImGuiStorage to Disk

### Step 1: Access the Storage Object

Retrieve the storage instance for the current window. You must call this between `ImGui::Begin()` and `ImGui::End()` for the specific window whose state you want to save.

```cpp
ImGui::Begin("MyPersistentWindow");
ImGuiStorage* storage = ImGui::GetStateStorage();  // Returns storage for current window
// ... UI construction ...
ImGui::End();

```

Alternately, replace the storage entirely with a custom instance using `ImGui::SetStateStorage(&my_storage)` before calling `ImGui::Begin()`.

### Step 2: Iterate and Export Data

The `Data` member is a public vector you can iterate directly. Each entry contains the key and value, though you must track the value type yourself since the union does not store type information.

```cpp
void SaveStorageToJSON(const char* filename, ImGuiStorage* storage)
{
    nlohmann::json j = nlohmann::json::array();
    
    for (int i = 0; i < storage->Data.Size; ++i)
    {
        const ImGuiStoragePair& p = storage->Data[i];
        // You must know the intended type; here we assume int for demonstration
        j.push_back({
            {"key", p.key},
            {"type", "int"},
            {"value", p.val_i}
        });
    }
    
    std::ofstream file(filename);
    file << j.dump(2);
}

```

### Step 3: Handle Type Information

Because `ImGuiStoragePair` uses a union, the container does not store which union member is active. Your serialization scheme must encode the type (int, float, or pointer) alongside the value. Common approaches include using a naming convention for keys (e.g., prefixing with `i_`, `f_`, `p_`) or maintaining a side map of known keys to types.

## Restoring UI State from Serialized Data

### Clearing and Rebuilding the Storage

When loading, clear the existing Data vector to prevent duplicate entries, then repopulate using the type-safe setter methods. The setter functions (implemented in [`imgui.cpp`](https://github.com/ocornut/imgui/blob/main/imgui.cpp)【/imgui.cpp#L2120-2150】) handle the binary search and insertion logic automatically.

```cpp
void LoadStorageFromJSON(const char* filename, ImGuiStorage* storage)
{
    std::ifstream file(filename);
    if (!file) return;
    
    nlohmann::json j;
    file >> j;
    
    storage->Data.clear();  // Remove existing entries
    
    for (const auto& elem : j)
    {
        ImGuiID key = elem["key"];
        std::string type = elem["type"];
        
        if (type == "int")
            storage->SetInt(key, elem["value"].get<int>());
        else if (type == "float")
            storage->SetFloat(key, elem["value"].get<float>());
        else if (type == "ptr")
            storage->SetVoidPtr(key, reinterpret_cast<void*>(elem["value"].get<uintptr_t>()));
    }
}

```

### Ensuring Stable ImGuiID Generation

**Critical:** `ImGuiID` values are hashed from string labels and the ID stack via `ImGui::GetID()`. If your UI hierarchy changes between sessions (e.g., adding or removing widgets), the IDs will shift, causing persisted values to attach to the wrong controls. To maintain stability, always use explicit ID pushes (`ImGui::PushID()`) with static integers or hashes of constant strings, ensuring the same ID sequence is generated every run.

## Practical Implementation Examples

### JSON Serialization Save/Load

This complete example demonstrates saving window state on exit and restoring it on startup using the nlohmann/json library.

```cpp
#include "imgui.h"
#include <fstream>
#include <nlohmann/json.hpp>

static void SaveImGuiState(const char* filename)
{
    ImGuiStorage* storage = ImGui::GetStateStorage();
    nlohmann::json j = nlohmann::json::array();

    for (int i = 0; i < storage->Data.Size; ++i) {
        const ImGuiStoragePair& p = storage->Data[i];
        // Determine type via convention: check if we stored this as int elsewhere
        j.push_back({ {"key", p.key}, {"type", "int"}, {"value", p.val_i} });
    }
    
    std::ofstream f(filename);
    f << j.dump(2);
}

static void LoadImGuiState(const char* filename)
{
    std::ifstream f(filename);
    if (!f) return;
    
    nlohmann::json j;
    f >> j;
    
    ImGuiStorage* storage = ImGui::GetStateStorage();
    storage->Data.clear();

    for (auto& e : j) {
        ImGuiID id = e["key"];
        std::string t = e["type"];
        if (t == "int") storage->SetInt(id, e["value"]);
        else if (t == "float") storage->SetFloat(id, e["value"]);
    }
}

// Usage in main loop:
// LoadImGuiState("ui_state.json");  // Before first ImGui::Begin
// ... render loop ...
// SaveImGuiState("ui_state.json");   // On application exit

```

### Custom Global Storage Replacement

For state that must survive window destruction, allocate a persistent `ImGuiStorage` instance and assign it to the window context before rendering.

```cpp
// Global or static member
static ImGuiStorage g_PersistentStorage;

// During initialization
ImGui::SetStateStorage(&g_PersistentStorage);
ImGui::Begin("MyWindow");
// All GetStateStorage() calls now return g_PersistentStorage
ImGui::End();

```

### Persisting Selection Sets

ImGui uses storage internally to track selection states in widgets like `Selectable`. You can leverage this pattern for custom multi-selection persistence.

```cpp
ImGuiStorage* sel = ImGui::GetStateStorage();

for (int i = 0; i < items.size(); i++) {
    ImGuiID id = ImGui::GetID(items[i].label.c_str());
    bool selected = sel->GetInt(id, 0) != 0;
    
    if (ImGui::Selectable(items[i].label.c_str(), selected)) {
        sel->SetInt(id, !selected);  // Toggle state
    }
}
// Serialize 'sel' using the JSON method above to persist selection across runs

```

## Summary

- **ImGuiStorage** stores per-window state as a sorted vector of `ImGuiStoragePair` in [`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h), supporting only **int**, **float**, and **void\*** types.
- Access the active storage via `ImGui::GetStateStorage()` or replace it with `ImGui::SetStateStorage()` before calling `ImGui::Begin()`.
- Serialization requires manually iterating `storage->Data`, writing the `ImGuiID` keys and values to disk, and tracking type information externally since the union does not encode it.
- Restoration involves clearing `Data` and repopulating via `SetInt()`, `SetFloat()`, or `SetVoidPtr()`, ensuring that `ImGuiID` generation remains stable between sessions.
- According to the implementation in [`imgui.cpp`](https://github.com/ocornut/imgui/blob/main/imgui.cpp)【/imgui.cpp#L2120-2150】, the setter methods automatically handle binary search insertion to maintain the sorted invariant required for O(log n) lookups.

## Frequently Asked Questions

### Does ImGuiStorage support automatic serialization to imgui.ini?

No. While Dear ImGui writes window positions and sizes to the INI file automatically, it does not serialize `ImGuiStorage` contents. The `ImGuiStorage` class defined in [`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h)【/imgui.h#L2794-L2815】 contains no serialization methods, so you must implement custom save/load logic using the public `Data` vector.

### How do I store complex data types in ImGuiStorage?

You cannot store complex types directly. **ImGuiStorage** only accepts **int**, **float**, and **void\*** values. For complex data, store a pointer to your object in the `void*` slot using `SetVoidPtr()`, or hash your complex state into an integer representation. Ensure that the pointer remains valid for the duration of the storage's lifetime.

### Why are my persisted values loading onto the wrong widgets?

This occurs when `ImGuiID` values change between sessions. Since IDs are generated by hashing the ID stack and string labels (via `ImGui::GetID()`), any modification to your UI hierarchy (adding, removing, or reordering widgets) shifts the IDs. Use explicit `ImGui::PushID()` calls with static integer constants or constant string hashes to guarantee stable IDs across application runs.

### Can I share ImGuiStorage between multiple windows?

Yes. Create a persistent `ImGuiStorage` instance and call `ImGui::SetStateStorage(&my_storage)` immediately before each `ImGui::Begin()` call for the windows you want to link. According to [`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h)【/imgui.h#L1047-1048】, this replaces the window's default storage pointer with your custom one, causing those windows to share the same key-value space.