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.default]
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 non-secret state and last-used values
Calendar credentialsDesktop Secret Service or an explicit CalDAV password fileCalDAV passwords and Google refresh tokens
Encrypted storage keyDesktop Secret Service or an explicit key filemaster key for encrypted clipboard history and calendar events
Calendar event cache$XDG_CACHE_HOME/noctalia/calendar/events.encencrypted last-known-good events
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 three storage owners:

  • Yours - ~/.config/noctalia/ (settings you write) and ~/.local/share/noctalia/ (plugins you install).
  • Noctalia’s - ~/.local/state/noctalia/ (GUI overrides, UI state, plugin repo caches, exported plugin files, and downloaded catalogs).
  • Credential provider’s - durable calendar credentials and the encrypted storage master key managed by Secret Service, or explicit credential/key files managed by an external provisioner such as agenix. Deleting Noctalia’s state directory does not remove them.

Every path honors the relevant standard XDG_CONFIG_HOME / XDG_STATE_HOME / XDG_DATA_HOME / XDG_CACHE_HOME variable (and the NOCTALIA_CONFIG_HOME / NOCTALIA_STATE_HOME / NOCTALIA_DATA_HOME overrides where applicable).

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

Every diagnostic is prefixed with the file, line, and column it came from, followed by the dotted config path:

WARN /home/you/.config/noctalia/bar.toml:14:10: bar.main.heigth: unknown setting
ERROR /home/you/.config/noctalia/widgets.toml:22:12: widget.clock.timezone: unknown timezone "Europe/Berln"

With several config files in play (or settings.toml overriding one of them), the reported file is the one whose definition actually won the merge.

It reports two severities:

  • Errors - problems that stop the configuration from loading correctly: TOML syntax errors, values that fail to parse such as an invalid color, invalid cross-field combinations such as a keyboard layout with both its glyph and label hidden, 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.

The shell log carries the same prefix, and the on-screen config-error notification puts the location in its title:

config.toml:22:12
Error: widget.clock.timezone: unknown timezone "Europe/Berln"

There the file is shown relative to your config directory (or as a bare file name for settings.toml), so a bad value points straight at the line that set it. When the position cannot be determined, the notification falls back to a Config error title.