Skip to content

How configuration works

Noctalia can be configured in two ways: files you write yourself, and settings the app saves for you.

For most people, the important rule is simple: put your own configuration in ~/.config/noctalia/. If something changed in the GUI and now seems to ignore your file, check ~/.local/state/noctalia/settings.toml.

Your hand-written config lives in:

  • $NOCTALIA_CONFIG_HOME/noctalia/
  • $XDG_CONFIG_HOME/noctalia/
  • or ~/.config/noctalia/

Noctalia reads every *.toml file in that folder, sorts them alphabetically, and merges them into one config. A single config.toml is the easiest setup:

~/.config/noctalia/config.toml
[theme]
mode = "dark"
[bar.main]
position = "top"

You can also split things up if you like tidy dotfiles:

~/.config/noctalia/
├── bar.toml
├── theme.toml
└── widgets.toml

GUI-managed overrides live in:

  • $NOCTALIA_STATE_HOME/noctalia/settings.toml
  • $XDG_STATE_HOME/noctalia/settings.toml
  • or ~/.local/state/noctalia/settings.toml

Noctalia writes this file when you change settings through the UI, setup flows, IPC-backed controls, and other runtime actions that need to persist. If the file is a symbolic link, Noctalia writes through to the link target and keeps the link in place.

NOCTALIA_CONFIG_HOME and NOCTALIA_STATE_HOME have the same “home root” shape as the XDG variables. For example, NOCTALIA_CONFIG_HOME=/tmp/profile reads /tmp/profile/noctalia/. Use these Noctalia-specific variables when you want a separate shell profile without changing the environment inherited by apps launched from Noctalia.

Automatic loading only sees *.toml files sitting directly in the config folder. To pull in files from subdirectories, load a specific file, or control load order without renaming files, add an [include] table:

~/.config/noctalia/config.toml
[include]
files = [
"widgets/", # a directory: every *.toml inside it (sorted, non-recursive)
"bars/top.toml", # a single file
"~/.config/shared/base.toml", # ~ , $VAR and ${VAR} are expanded
]
# the rest of this file's settings go here as usual
[theme]
mode = "dark"

Each entry is either a directory (which loads every *.toml directly inside it, sorted alphabetically) or a single file. Relative paths resolve against the directory of the file doing the including, so a file in profiles/work.toml can reach a shared bundle with "../widgets/". Paths starting with ~, or containing $VAR / ${VAR}, are expanded.

The including file wins. Included files are merged first, as a reusable base, and then the file’s own settings are layered on top. So a value you set directly in config.toml overrides the same value pulled in from an include:

[include]
files = ["bars/base.toml"] # base.toml sets bar.top.thickness = 30
[bar.top]
thickness = 40 # this wins - the bar ends up 40

Includes can nest (an included file can include more files), and each file is loaded at most once - if two files include the same file, it is merged once and a cycle is detected and skipped rather than looping forever.

By default, every *.toml in the config folder still loads automatically alongside your includes. If you keep alternative setups in the root that should not all load at once - say profile_a.toml and profile_b.toml - set autoload = false in your entry file. Then only the file(s) that set it (plus whatever they include) load, and the other root files are ignored:

~/.config/noctalia/config.toml
[include]
autoload = false
files = ["profiles/work.toml"]

autoload only has an effect in a file loaded directly from the config folder; it is ignored in included files. [include] is honored in your config files only - it has no effect in the app-managed settings.toml.

Noctalia loads configuration in this order:

  1. Built-in defaults
  2. Your *.toml files in the resolved config directory - for each file, anything it pulls in via [include] first, then the file’s own settings on top
  3. GUI-managed overrides in the resolved state directory’s settings.toml

Because settings.toml loads last, it wins when it contains the same setting as your hand-written config layer. When Settings writes a value that matches the parsed value from the lower layers, Noctalia removes that redundant key instead of keeping it as a GUI override.

Use ~/.config/noctalia/ for curated config: the things you want to keep in dotfiles, copy to another machine, or manage declaratively.

Treat ~/.local/state/noctalia/settings.toml as an app-managed override file. It is useful to inspect when debugging GUI changes, and safe to delete when you want to clear those overrides. Keeping it outside ~/.config also lets the GUI save changes when your config directory is read-only, for example on NixOS. Support reports include config sources and settings.toml, but omit state.toml contents because they can contain private runtime data.

Both layers are watched for changes and hot-reloaded. If neither layer exists, Noctalia uses its built-in defaults.

When a release changes the shape of a setting, Noctalia migrates old values before parsing them so the running shell keeps the intended behavior. The app-owned settings.toml is upgraded once and stamped with an internal config_version; Noctalia manages that marker and excludes it from exported user config.

Files under ~/.config/noctalia/ are never rewritten. Values that need migration are normalized in memory on every load, and Settings keeps a warning visible until you update the source TOML. Noctalia also sends a notification when it first finds an affected path, when a new affected path appears, and every three days while affected values remain.

Run the validator for every affected path and its canonical replacement:

Terminal window
noctalia config validate

Noctalia can keep using normalized values while you update a file, but migration warnings are intentionally not permanent compatibility promises. Treat them as required config maintenance.

Where Noctalia keeps everything, by purpose:

WhatWherePurpose
Your config~/.config/noctalia/config.tomlhand-written / declarative - the base layer
GUI overrides~/.local/state/noctalia/settings.tomlwritten by Settings; wins over your config
Internal UI state~/.local/state/noctalia/state.tomlapp-managed (calendar account tokens, last-used values)
Custom palettes~/.config/noctalia/palettes/your own color palette files
Local plugins~/.local/share/noctalia/plugins/plugins you install or place by hand
Plugin source repos~/.local/state/noctalia/plugins/sources/git-source repo caches, re-fetchable
Exported plugin files~/.local/state/noctalia/plugins/materialized/runtime files exported from git sources
Community palettes / templates~/.local/state/noctalia/community-*/downloaded catalogs, re-fetchable

It comes down to two buckets:

  • Yours - ~/.config/noctalia/ (settings you write) and ~/.local/share/noctalia/ (plugins you install).
  • Noctalia’s - ~/.local/state/noctalia/ (everything the app writes or fetches: GUI overrides, UI state, plugin repo caches, exported plugin files, downloaded catalogs) - app-managed and regenerable, so it’s always safe to delete.

Every path honors the standard XDG_CONFIG_HOME / XDG_STATE_HOME / XDG_DATA_HOME variables (and the NOCTALIA_CONFIG_HOME / NOCTALIA_STATE_HOME / NOCTALIA_DATA_HOME overrides).

Export the merged user config from the command line:

Terminal window
noctalia config export > noctalia-config.toml

Export the full effective config, including built-in defaults:

Terminal window
noctalia config export full > noctalia-full-config.toml

Export stops with an error (and writes nothing) if a config file fails to parse or an [include] points at a file that does not exist, so a broken config never produces a misleading partial export. (The running shell is more forgiving: it loads what it can and surfaces the problem as a notification.)

Because export writes TOML to stdout, you can pipe it into TOML-aware query tools. For example, with yq:

Terminal window
noctalia config export full | yq -p toml -r '.theme.mode'
noctalia config export full | yq -p toml -r '.shell.offline_mode'
noctalia config export full | yq -p toml '.bar.default'

If you prefer normal jq filters, convert the TOML stream to JSON first:

Terminal window
noctalia config export full | yq -p toml -o json '.' | jq -r '.theme.mode'

The Settings actions menu includes Export Config…. It opens a chooser with two export modes:

  • Merged User Config is recommended for dotfiles. It merges your explicit config files and GUI-managed overrides into one TOML file, while leaving built-in defaults implicit.
  • Full Effective Config exports the active config snapshot, including built-in defaults. Use it for inspection or sharing an exact state, but avoid treating it as your long-term curated config because it pins defaults that could otherwise evolve.

Run the validator to catch problems before they silently take effect:

Terminal window
noctalia config validate

With no argument it checks the merged configuration exactly as the shell loads it: every *.toml in ~/.config/noctalia/ (or $NOCTALIA_CONFIG_HOME), then the settings.toml overrides in ~/.local/state/noctalia/. Pass a directory to validate just that directory’s *.toml files instead:

Terminal window
noctalia config validate ./my-config-dir

Pass a file to validate only that TOML file, without scanning default locations or merging settings.toml. This is useful for generated configs, including NixOS-managed config files:

Terminal window
noctalia config validate ./config.toml

It reports two severities:

  • Errors - problems that stop the configuration from loading correctly: TOML syntax errors (with file, line, and column), values that fail to parse such as an invalid color, an [include] whose files entry points at a path that does not exist, or a malformed [include] table (autoload that is not a boolean, files that is not a list of strings). These exit with status 1.
  • Warnings - things the shell loads fine but ignores, adjusts, or migrates in memory: unknown/obsolete sections and settings (misspelled or left over from an older version), values with a canonical replacement, values outside the allowed range (clamped), and invalid enum choices. Warnings are advisory and do not fail the check. Unknown widget and desktop-widget settings are checked against each widget type’s known options.

A config with only warnings still prints ✓ Config is valid and exits 0 (with the warnings listed), so unknown keys won’t break a pre-commit hook or CI step - they’re surfaced as cleanup hints. Only genuine errors exit 1.