Application Settings and Preferences

Updated

September 12, 2026

Application Settings and Preferences

CLIs are not the only thing in $HOME. Terminals, editors, and browsers all grow JSON/TOML/dconf trees. The boring default is: use a Home Manager module when one exists; generate the native file from Nix; do not manage files the app rewrites on every launch.

Mental model

Home Manager modules compile Nix into the format the app already understands:

Module Writes
programs.alacritty ~/.config/alacritty/alacritty.toml
programs.kitty ~/.config/kitty/kitty.conf
programs.vscode settings.json + extensions from nixpkgs
programs.ssh ~/.ssh/config
programs.tmux ~/.config/tmux/tmux.conf

If there is no module, xdg.configFile."app/config".text is still better than a README. If the app treats the config as a database (window geometry, crash dumps, “recent files”), leave it unmanaged. Home Manager 26.05 will fail the switch when it tries to overwrite a file the app rewrote, unless you set home-manager.backupFileExtension (NixOS module) or stop managing that path.

Skip: browser profiles, ~/.config/Code/User/globalStorage, ~/.local/share/*/state, anything SQLite. Manage: the TOML/JSON the app documents as configuration.

Worked examples

Case 1: Alacritty

Save as apps.nix:

# apps.nix
{ pkgs, ... }:

{
  programs.alacritty = {
    enable = true;
    settings = {
      window.opacity = 0.95;
      font.size = 12.0;
      font.normal.family = "JetBrains Mono";
      colors.primary.background = "#1e1e1e";
    };
  };
}
home-manager switch
test -f ~/.config/alacritty/alacritty.toml && echo ok

Output:

ok

Open Alacritty. Opacity and font come from this generation. A distro-packaged Alacritty that ignores XDG is a different binary — install Alacritty via the same Home Manager module (programs.alacritty.enable already installs the package).

Case 2: SSH client config

# ssh.nix
{
  programs.ssh = {
    enable = true;
    # Home Manager 26.05: RFC 42 programs.ssh.settings is the new form.
    # matchBlocks still works; it is deprecated and migrated automatically.
    extraConfig = ''
      Host *
        IdentityFile ~/.ssh/id_ed25519
        IdentitiesOnly yes
    '';
    matchBlocks = {
      "desk-bastion" = {
        hostname = "bastion.desk.internal";
        user = "deskadmin";
        port = 22;
      };
      "desk-*" = {
        proxyJump = "desk-bastion";
        user = "deskadmin";
      };
    };
  };
}

Host keys and private keys are not in this file. IdentityFile points at ~/.ssh/id_ed25519, which you create with ssh-keygen or decrypt with sops. Putting a private key in .text copies it into /nix/store.

When you migrate, programs.ssh.settings is the 26.05 knob (same ideas: Host, HostName, User). Do not invent a second ~/.ssh/config via home.file while the module is enabled — two writers, one file.

Case 3: tmux

# tmux.nix
{ pkgs, ... }:

{
  programs.tmux = {
    enable = true;
    clock24 = true;
    keyMode = "vi";
    terminal = "tmux-256color";
    extraConfig = ''
      bind r source-file ~/.config/tmux/tmux.conf
    '';
    plugins = with pkgs.tmuxPlugins; [ sensible yank ];
  };
}

Plugins are store paths, same story as neovim.

Case 4: VS Code extensions from nixpkgs

# vscode.nix
{ pkgs, ... }:

{
  programs.vscode = {
    enable = true;
    # 26.05: extensions and userSettings live on the profile.
    # Top-level userSettings is obsolete (renamed to profiles.default.*).
    profiles.default = {
      extensions = with pkgs.vscode-extensions; [
        jnoortheen.nix-ide
        tamasfe.even-better-toml
      ];
      userSettings = {
        "editor.tabSize" = 2;
        "files.insertFinalNewline" = true;
      };
    };
  };
}

Marketplace extensions that are not in nixpkgs will not appear here. That is inconvenient and correct: an unsigned VSIX from the internet is not a pinned input. Use pkgs.vscode-utils.extensionFromVscodeMarketplace only when you pin sha256.

VS Code forks on Home Manager 26.05 have their own modules (programs.vscodium, programs.cursor, …). Do not set programs.vscode.package = pkgs.vscodium — that pname knob was removed.

Do not manage ~/.config/Code/User/globalStorage or workspaceStorage. Those directories are rewritten on every launch. If home-manager switch errors … exists but is not a symlink, either backupFileExtension = "bak" once, or remove the path from Home Manager. Do not force = true on a database the editor holds open.

Case 5: Fonts the terminal named

# fonts.nix
{ pkgs, ... }:

{
  fonts.fontconfig.enable = true;
  home.packages = with pkgs; [
    jetbrains-mono
    nerd-fonts.jetbrains-mono
  ];
}

programs.alacritty.settings.font.normal.family = "JetBrains Mono"; does nothing if the font is not installed. Put the font in home.packages (or NixOS fonts.packages).

The trap

The trap is managing ~/.config/Code/User/globalStorage or a browser profile. Those directories are mutable state. Home Manager will either clobber your session or fail the switch.

The other trap is committing programs.ssh.extraConfig with IdentityFile /nix/store/…-id_ed25519. That store path is world-readable. Keys live outside the store, or in a sops-decrypted tmpfs.

A third: programs.vscode.userSettings on 26.05 (obsolete — use profiles.default). A fourth: force = true on a file the app rewrites so every switch fights the running editor.

The boring rule

  • Modules first, xdg.configFile second, unmanaged mutable state last.
  • Terminal + font + editor settings are code. Window geometry, globalStorage, browser profiles are not.
  • SSH config is code (settings / migrated matchBlocks). SSH keys are not.
  • VS Code: profiles.default on 26.05; extensions from nixpkgs (or a pinned marketplace fetch). Forks get their own module.
  • If home-manager switch fights the app, stop managing that path. backupFileExtension is a one-shot, not a lifestyle.

Try this

  1. Set Alacritty font.size to 14.0, switch, open a new terminal, then set it back.
  2. Add an SSH matchBlocks entry for a lab host and ssh -G desk-bastion | grep hostname.
  3. readlink -f ~/.config/alacritty/alacritty.toml — store path.
  4. Intentionally add a private key as home.file.".ssh/id_ed25519".text = "-----BEGIN …"; in a throwaway VM, nix-store -q --hash that path, then delete it and GC. That hash is why we never do this.
  5. Launch Alacritty, change opacity in the GUI if it has one, switch again. If HM fights, that file is not yours — drop it from the module.