# How OpenLogi Uses TOML for Configuration: Schema Versioning and File Management

> Discover how OpenLogi manages configuration using TOML for schema versioning and file management. Learn about its robust validation and atomic disk operations for reliable settings.

- Repository: [Xuan Zhang/OpenLogi](https://github.com/AprilNEA/OpenLogi)
- Tags: how-to-guide
- Published: 2026-08-29

---

**OpenLogi persists all user settings in a strictly-versioned TOML file controlled by the `Config` struct in [`crates/openlogi-core/src/config.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/config.rs), using serde for validation and a dedicated `ConfigFile` utility for atomic disk operations.**

OpenLogi manages device settings, keyboard mappings, and application preferences through a human-readable TOML configuration system. The implementation in the AprilNEA/OpenLogi repository employs strict schema validation with `#[serde(deny_unknown_fields)]` and automatic migrations to ensure settings survive software updates. This guide examines the source code architecture, file I/O operations, and versioning strategy behind OpenLogi's configuration management.

## Configuration File Location and Format

OpenLogi stores the active configuration in platform-standard directories using the filename [`config.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/config.toml).

- **Linux**: `~/.config/openlogi/config.toml`
- **Windows**: `%APPDATA%\OpenLogi\config.toml`
- **macOS**: `~/Library/Application Support/OpenLogi/config.toml`

The repository provides a fully documented reference file at [`docs/config.example.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/docs/config.example.toml) that demonstrates all supported sections and default values. Users can copy this example to the appropriate location to initialize their settings.

## Core Configuration Architecture

The central `Config` struct defined in [`crates/openlogi-core/src/config.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/config.rs) serves as the in-memory representation of the TOML document. The implementation derives `Serialize` and `Deserialize` from serde and applies `#[serde(deny_unknown_fields)]` to enforce strict correspondence between the file structure and the Rust type system.

Key structural components include:

- **`schema_version`**: An integer field tracking the on-disk format version
- **`app_settings`**: Global application preferences
- **`selected_device`**: Identifier for the currently active device
- **`devices`**: A `BTreeMap<String, DeviceConfig>` providing deterministic ordering of per-device configurations
- **`keyboard`**: Global keyboard-wide bindings stored in a `KeyboardConfig` section

The use of `#[serde(default)]` on optional sections ensures that older configuration files missing new fields still deserialize correctly without errors.

## Schema Versioning and Migration Strategy

OpenLogi maintains a `SCHEMA_VERSION` constant (currently set to `6`) in [`crates/openlogi-core/src/config.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/config.rs) that represents the latest supported format. When loading a configuration file, the system compares the file's `schema_version` field against this constant.

If the file uses an older schema, OpenLogi executes specific migration functions before the configuration becomes active. The source code includes methods such as:

- **`migrate_transport_scoped_keys()`**
- **`migrate_owner_locked_gestures()`**

These functions rewrite deprecated keys into the current schema layout, ensuring user settings persist across software updates. After migration, the system persists the updated configuration back to disk with the current schema version.

## File I/O and Atomic Operations

The `ConfigFile` helper struct in [`crates/openlogi-core/src/config/file.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/config/file.rs) abstracts all disk operations, providing safe loading, saving, and backup handling. This module uses `toml::from_str` for deserialization and `toml::to_string_pretty` for generating human-readable output.

Critical operations include:

- **`ConfigFile::load()`**: Reads the TOML from the platform configuration directory and applies any necessary migrations
- **`ConfigFile::save_atomic()`**: Writes updates to disk using atomic file operations to prevent corruption during write failures
- **`Config::ephemeral()`**: Creates a temporary in-memory configuration for unit testing that never writes to disk, protecting developer settings during test runs

When the OpenLogi agent starts, it loads the file via `ConfigFile::load()` and populates the in-memory `Config`. Subsequent UI actions mutate the `Config` object, and the agent then calls `ConfigFile::save_atomic()` to persist changes.

## Practical Implementation Examples

The following Rust code demonstrates loading, modifying, and persisting configuration values:

```rust
// Load persistent configuration from disk
let cfg = openlogi_core::config::ConfigFile::load()?;

// Configure DPI presets for a specific device
cfg.set_dpi_presets("receiver:abcd:slot:1", vec![800, 1200, 1600]);

// Add a global keyboard shortcut
use openlogi_core::config::{KeyTrigger, Action, KeyModifiers};
cfg.set_keyboard_binding(
    KeyTrigger::new(KeyModifiers::default(), "F1".into()),
    Some(Action::Mute),
);

// Persist changes atomically to prevent data corruption
openlogi_core::config::ConfigFile::save_atomic(&cfg)?;

```

A minimal valid configuration file appears as follows:

```toml

# docs/config.example.toml structure

schema_version = 6

[app_settings]
language = "en"
ui_scale = 1.0

[keyboard]
[[keyboard.bindings]]
trigger = { modifiers = [], key = "F1" }
action = "mute"

[devices."receiver:abcd:slot:1"]
custom_name = "My Mouse"
dpi_presets = [800, 1200, 1600]
smartshift = { enabled = true, threshold = 2 }

```

## Summary

- OpenLogi stores settings in [`config.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/config.toml) located in platform-standard configuration directories
- The `Config` struct in [`crates/openlogi-core/src/config.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/config.rs) uses `#[serde(deny_unknown_fields)]` for strict TOML validation
- Schema versioning via `SCHEMA_VERSION` (currently 6) enables automatic migrations from older file formats
- The `ConfigFile` utility in [`crates/openlogi-core/src/config/file.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/config/file.rs) handles atomic writes and safe loading operations
- Per-device settings use a `BTreeMap<String, DeviceConfig>` for deterministic ordering
- Ephemeral configurations support testing without affecting production settings

## Frequently Asked Questions

### Where does OpenLogi store the configuration TOML file?

OpenLogi follows platform conventions for configuration storage. On Linux, the file resides at `~/.config/openlogi/config.toml`; on Windows at `%APPDATA%\OpenLogi\config.toml`; and on macOS at `~/Library/Application Support/OpenLogi/config.toml`. The `ConfigFile::load()` method automatically resolves these paths based on the operating system.

### What happens to my settings when I upgrade OpenLogi?

The configuration system automatically migrates existing settings. When the TOML file loads, OpenLogi compares its `schema_version` field against the current `SCHEMA_VERSION` constant. If the versions differ, migration functions such as `migrate_transport_scoped_keys()` execute to transform deprecated fields into the new format before the configuration becomes active.

### Can I edit the OpenLogi configuration file manually?

Yes. The TOML format is human-readable and editable. The repository includes [`docs/config.example.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/docs/config.example.toml) as a reference showing all valid sections including `app_settings`, `devices`, and `keyboard` bindings. However, because the parser uses `#[serde(deny_unknown_fields)]`, any typos or unsupported keys will cause deserialization errors on startup.

### How does OpenLogi prevent configuration corruption during saves?

The `ConfigFile::save_atomic()` method in [`crates/openlogi-core/src/config/file.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/config/file.rs) implements atomic write operations. This ensures that the configuration file is either completely written or not modified at all, preventing partial writes that could occur during system crashes or power failures from corrupting the user's settings.