XDG Base Directory Specification¶
Most configs in this repo live under ~/.config/, ~/.local/share/, ~/.local/state/, or ~/.cache/ rather than dot-files in $HOME. That layout follows the XDG Base Directory Specification.
Core environment variables¶
| Variable | Default | Purpose |
|---|---|---|
XDG_CONFIG_HOME |
~/.config |
Per-user config files |
XDG_DATA_HOME |
~/.local/share |
Per-user application data |
XDG_STATE_HOME |
~/.local/state |
Per-user state (logs, history, undo) |
XDG_CACHE_HOME |
~/.cache |
Per-user non-essential cached data |
XDG_RUNTIME_DIR |
(set by login) | Per-session runtime files (sockets, etc.) |
Why it matters¶
- Clean
$HOME: the old "every app dumps a~/.foodotfile" pattern gets noisy fast. - Predictable tooling: backup, sync, dotfile managers (like chezmoi) all benefit from one well-known tree.
- Easy to nuke: wipe
~/.cache/foowithout touching configs; wipe~/.local/state/foowithout losing settings.
Choosing the right directory¶
When writing a new tool — or deciding where to redirect an existing one's state — use this cheat-sheet. The first four rows are XDG-spec; the last two are what programs typically reach for once they leave the spec's coverage.
| Data category | Where it goes | Default path | Notes |
|---|---|---|---|
| Important config (user must back up) | XDG_CONFIG_HOME |
~/.config/mytool/ |
Editable, declarative — config.toml, keymap.json. Survives uninstall/reinstall; safe to commit to a dotfiles repo. |
| Important data (user must back up) | XDG_DATA_HOME |
~/.local/share/mytool/ |
Generated by the app but irreplaceable: notes DB, plugin store, downloaded models, generated assets. |
| Persistent state (machine-local, not portable) | XDG_STATE_HOME |
~/.local/state/mytool/ |
History, undo trees, last-opened files, cursor position, log files. Annoying to lose but auto-rebuilds. |
| Reconstructible cache | XDG_CACHE_HOME |
~/.cache/mytool/ |
Compiled artifacts, downloaded blobs, thumbnail tiles. Wipe anytime. |
| Per-session runtime files | XDG_RUNTIME_DIR |
/run/user/$UID/mytool/ |
Unix sockets, lock files, named pipes. Mode 0700, tmpfs, deleted on logout. Linux-only — macOS does not set this; fall back to $TMPDIR. |
| One-shot ephemeral scratch | $TMPDIR (or /tmp) |
/tmp (Linux), /var/folders/.../T/ (macOS) |
Process-scoped intermediate files. Not XDG — POSIX/FHS territory. Deleted on reboot or by systemd-tmpfiles / tmpwatch. Always honor $TMPDIR when set; mktemp -d -p "${TMPDIR:-/tmp}". |
Decision rules, in order:
- "Will the user be sad if it disappears?" — yes →
CONFIG_HOMEorDATA_HOME. No → one of the lower tiers. - "Did the user hand-write it?" — yes →
CONFIG_HOME. No, but app-generated and irreplaceable →DATA_HOME. - "Could I regenerate it from source/network?" — yes →
CACHE_HOME. No, and it's nice-to-keep across runs →STATE_HOME. - "Does it need a Unix socket / lock other processes can find?" —
RUNTIME_DIR(Linux) /$TMPDIR(macOS). Use a stable name ($XDG_RUNTIME_DIR/mytool.sock) so peers can locate it. - "Scoped to a single process invocation?" —
mktemp -d -p "${TMPDIR:-/tmp}" mytool.XXXXXXandtrap 'rm -rf "$tmp"' EXIT.
Common gotchas:
XDG_RUNTIME_DIRis not set on macOS by default (no systemd-logind). Hardcoding it breaks; the cross-platform idiom is${XDG_RUNTIME_DIR:-${TMPDIR:-/tmp}}./tmpon Linux may or may not be tmpfs (distro /systemd-tmpfilesdependent). Do not assume it survives a reboot, and do not assume it persists across one either.- The XDG spec says nothing about
/tmp— that's POSIX/FHS. Don't conflate the two. XDG_STATE_HOMEwas only added in spec v0.8 (2021). Older tools predate it and often dump state intoXDG_DATA_HOMEor~/.local/share/; that's a historical wart, not a bug to "fix" upstream.- macOS's
~/Library/Application Support/is the platform-native equivalent ofXDG_DATA_HOME; many cross-platform apps detect Darwin and use it instead. That's expected — it's not a regression.
Tool-by-tool status (in this repo)¶
| Tool | XDG-native? | Where we put it |
|---|---|---|
| tmux | Yes (3.1+) | ~/.config/tmux/tmux.conf (+ shim at ~/.tmux.conf) |
| Neovim | Yes | ~/.config/nvim/ |
| Zellij | Yes | ~/.config/zellij/ |
| Starship | Yes | ~/.config/starship.toml |
| Alacritty | Yes | ~/.config/alacritty/ |
| Ghostty | Yes | ~/.config/ghostty/ |
| Yazi | Yes | ~/.config/yazi/ |
| bat | Yes | ~/.config/bat/ |
| direnv | Yes | ~/.config/direnv/ |
| gh | Yes | ~/.config/gh/ |
| gh-dash | Yes | ~/.config/gh-dash/ |
| LazyGit | Yes | ~/.config/lazygit/ |
| Sesh | Yes | ~/.config/sesh/ |
| uv | Yes | ~/.config/uv/ |
| Bun | Yes | ~/.config/.bunfig.toml |
| Homebrew Bundle | N/A | ~/.config/homebrew/ (our convention) |
| Claude Code | No | ~/.claude/ |
| SSH | No | ~/.ssh/ (spec doesn't cover it) |
| Git | Partial | ~/.gitconfig (repo also uses ~/.config/git/hooks/) |
| npm | Yes | ~/.npmrc (via $HOME; XDG support exists but most tooling still writes ~/.npmrc) |
| Cargo | Partial | ~/.cargo/config.toml (respects CARGO_HOME, not XDG directly) |
Tools marked "No" or "Partial" keep legacy $HOME paths because upstream hasn't migrated or because the tool predates the spec; we follow whatever the tool actually reads.
How this repo handles it¶
- chezmoi source paths like
dot_config/<tool>/...map to~/.config/<tool>/...on apply. That is the primary way we opt into XDG. - Legacy paths (e.g.
~/.tmux.conf,~/.gitconfig) are kept as shims or top-level files when a tool expects them, often sourcing the real XDG-located config. - For state/data/cache: we rarely commit those — they're generated at runtime and deliberately excluded from the repo.
Further reading¶
- freedesktop.org spec
- Arch Wiki — XDG Base Directory — an up-to-date catalog of which tools honor the spec natively and which need workarounds.