This page describes how PowerToys stores and loads settings on disk: the file locations, JSON schemas, the code components responsible for reading and writing, and the data flow from a user action in the Settings UI to a file write. For documentation of the Settings UI application itself and its MVVM structure, see 4.1. For the IPC mechanism that carries settings changes between the runner and the Settings UI, see 2.4.
All PowerToys settings files are stored under a single root folder resolved at runtime by PTSettingsHelper::get_root_save_folder_location(), which resolves to %LOCALAPPDATA%\Microsoft\PowerToys\.
| File path (relative to root) | Purpose | Primary writer |
|---|---|---|
general_settings.json | Global settings: module enabled states, theme, tray icon, update preferences, Quick Access shortcut | PTSettingsHelper::save_general_settings() |
<ModuleName>\settings.json | Per-module settings (hotkeys, behavior toggles, etc.) | Each module's set_config() implementation |
language.json | Language override for the Settings UI | Runner on IPC language message |
UpdateState\state.json | GitHub update check state and version | Update worker via UpdateState::read() |
last_version_run | Version string from the last run, used to detect upgrades | PTSettingsHelper::save_last_version_run() |
oobe_opened | Flag recording whether OOBE has been shown | PTSettingsHelper::save_oobe_opened_state() |
Logs\runner.log | Runner process log | Logger |
Sources: src/runner/main.cpp493-495 src/runner/settings_window.cpp279-283 src/runner/main.cpp322-323 src/runner/main.cpp523-528
Settings persistence code entity diagram:
Sources: src/runner/general_settings.h1-42 src/runner/general_settings.cpp123-158 src/runner/settings_window.cpp151-192 src/runner/main.cpp560-606
general_settings.json SchemaThe schema is defined by GeneralSettings::to_json() in src/runner/general_settings.cpp82-121 and its C# mirror in src/settings-ui/Settings.UI.Library/GeneralSettings.cs Representative fields:
The enabled object maps each module's display name (as returned by get_key()) to a boolean. Module names used here must match exactly the string keys registered in the runner's modules() map.
Sources: src/runner/general_settings.cpp82-121 src/runner/general_settings.h12-36
When the runner (WinMain) starts, it loads and applies general settings before any modules are started:
Startup settings load sequence:
The critical detail: apply_general_settings is called with save=false at startup (src/runner/main.cpp563) because the modules() collection is not yet populated. Writing the file at this point would produce an incomplete enabled map.
Sources: src/runner/main.cpp560-606 src/runner/general_settings.cpp292-302 src/runner/general_settings.cpp501-565
When a user changes a setting in the Settings UI, the change travels via IPC pipe to the runner, which applies and persists it.
Settings change data flow diagram:
When a module is toggled on or off from the Dashboard, DashboardViewModel sends a minimal module_status message rather than the full general settings object:
{"module_status": {"FancyZones": false}}
The runner handles this in apply_module_status_update() (src/runner/general_settings.cpp196-289) by loading the existing general_settings.json, patching only the enabled.<ModuleName> field, and writing the file back. This avoids overwriting any settings fields not mirrored in the runner's in-memory state.
For full general settings changes (from GeneralPage), the Settings UI wraps the full GeneralSettings object as OutGoingGeneralSettings and sends it as {"general": {...}}. The runner's apply_general_settings() applies each field to the static globals (settings_theme, run_as_elevated, etc.) and, if anything changed, calls PTSettingsHelper::save_general_settings().
Change detection uses a simple JSON string comparison:
Sources: src/runner/settings_window.cpp194-357 src/runner/general_settings.cpp292-498 src/runner/general_settings.cpp196-289 src/settings-ui/Settings.UI/SettingsXAML/MainWindow.xaml.cs84-108 src/settings-ui/Settings.UI/ViewModels/DashboardViewModel.cs350-385
Each module is responsible for persisting its own settings. On the C++ runner side, the runner delegates via PowertoyModuleIface::set_config():
dispatch_json_config_to_modules()
โ send_json_config_to_module(key, json)
โ module->set_config(json)
The module implementation of set_config() typically:
%LOCALAPPDATA%\Microsoft\PowerToys\<ModuleName>\settings.jsonOn the C# Settings UI side, each module's settings page reads its own file directly via SettingsRepository<ModuleSettings>.GetInstance(SettingsUtils.Default). This means the Settings UI reads module settings from disk without going through the runner.
Sources: src/runner/settings_window.cpp151-192 src/settings-ui/Settings.UI/ViewModels/DashboardViewModel.cs519-525
The Settings UI uses a generic singleton repository pattern to manage per-type settings in memory.
| Class | Role |
|---|---|
SettingsRepository<T> | Singleton per T; caches SettingsConfig; raises SettingsChanged event |
ISettingsRepository<T> | Interface consumed by ViewModels |
SettingsUtils | File I/O: SaveSettings(), GetSettings() via the IFileSystem abstraction |
SettingsUtils.Default | Shared SettingsUtils instance used across all settings pages |
SettingsRepository<GeneralSettings>.GetInstance(SettingsUtils.Default) is called in multiple ViewModels (src/settings-ui/Settings.UI/SettingsXAML/Views/DashboardPage.xaml.cs37 src/settings-ui/Settings.UI/SettingsXAML/MainWindow.xaml.cs86). All callers receive the same singleton instance; changes to SettingsConfig are visible to all subscribers of SettingsChanged.
SettingsRepository flow (C# side):
Sources: src/settings-ui/Settings.UI/ViewModels/GeneralViewModel.cs99-130 src/settings-ui/Settings.UI/ViewModels/DashboardViewModel.cs109-133 src/settings-ui/Settings.UI/SettingsXAML/Views/DashboardPage.xaml.cs35-42
When the runner exits normally (message loop returns 0), it performs a final save of the entire in-memory general settings state to disk:
get_general_settings() (src/runner/general_settings.cpp160-193) constructs a GeneralSettings struct from the current static globals and the live is_enabled() state of every loaded module, producing a complete and authoritative snapshot. This is the canonical write that captures any state that may not have been flushed incrementally during the session.
If the runner exits with result != 0 (for example, when triggered by restart_maintain_elevation which calls PostQuitMessage(1)), this final save is skipped intentionally to avoid overwriting settings that were modified externally.
Sources: src/runner/main.cpp601-606 src/runner/general_settings.cpp160-193 src/runner/settings_window.cpp94-111
| Trigger | Function called | File written |
|---|---|---|
| Runner startup (initial apply) | apply_general_settings(loaded, save=false) | (none) |
| Module enabled toggle from Dashboard | apply_module_status_update(obj, save=true) | general_settings.json (read-modify-write) |
| Full general settings change from UI | apply_general_settings(obj, save=true) | general_settings.json |
| Module-specific settings change from UI | module->set_config(json) | <Module>/settings.json |
| Language change from UI | json::to_file(save_file_location, j) | language.json |
| Settings window opened (init write) | PTSettingsHelper::save_general_settings() | general_settings.json |
| Runner normal exit | PTSettingsHelper::save_general_settings(get_general_settings().to_json()) | general_settings.json |
| C# UI direct module toggle | SettingsUtils.Default.SaveSettings() | general_settings.json |
Sources: src/runner/main.cpp560-606 src/runner/general_settings.cpp196-498 src/runner/settings_window.cpp194-357 src/runner/settings_window.cpp512-513 src/settings-ui/Settings.UI/SettingsXAML/MainWindow.xaml.cs84-108
Refresh this wiki
This wiki was recently refreshed. Please wait 4 days to refresh again.