ConfigΒΆ
π This guide covers where every config file lives, when it runs, how overrides work, and how to extend the framework with your own code.
π Related documentsΒΆ
| Structure & boot | Architecture Β· Startup |
| Extending | Core (rc.d overrides) Β· Widgets Β· Hooks |
| Reference | Style Guide Β· Header Standard Β· Installers |
| Index | Documentation Index |
π§ Philosophy
The framework is opinionated by default but fully overridable. You should never need to edit framework files β everything is customizable from your config directory. Same basename in config replaces the framework file; different name adds.
Quick start (first time)ΒΆ
- Create your config dir: Run
zsh setup.zshfrom the framework repo (or use the cloner); this creates~/.config/zshand/with template files. - Or copy from samples:
cp -r samples/user-config/* ~/.config/zshand/then edit to taste. - Set env and secrets: Edit
env.zsh(EDITOR, PATH, framework flags) andsecrets.zsh(API keys; keepchmod 600). - Rebuild after changes: Run
zr(orzfullif using full bundle) thenexec zsh.
| I want to⦠| Put it here |
|---|---|
| Set env vars (EDITOR, PATH extras) | env.zsh |
| API keys, tokens | secrets.zsh only |
| Framework behavior (quiet, perf_budget, lazy_load) | config.toml |
| Manage installed packages & tools | packages.toml |
| Keybindings for widgets | keybindings.toml or [keybindings] in config.toml |
| Replace a core module (e.g. aliases) | rc.d/14_aliases.zsh (same basename) |
| Run before framework loads | init.d/*.zsh |
| Run after everything | post.d/*.zsh |
| Add a function / widget / hook | functions/, widgets/, hooks/ (any name) |
| Add OMZ/P10k plugins | plugins/ and optionally plugins.txt |
When each file or directory runsΒΆ
| File or directory | When it runs | What it's for |
|---|---|---|
init.d/ | Before any core module | Early PATH, env, feature flags |
env.zsh, secrets.zsh | Early init | Environment and secrets (auto-exported) |
config.toml | Very early (before env.zsh) | Framework behavior; section.key β ZSHAND_SECTION_KEY |
packages.toml | After config.toml (startup/18) | Package lists, deactivation, aliases, tools |
path.txt, bin/ | During PATH build | Extra PATH entries and personal scripts |
rc.d/ | Merged with core (02β90) | Override or insert between core modules |
hooks/, functions/, widgets/ | After framework dirs of same name | Your integrations; same basename = override |
aliases.zsh | After framework aliases | Your aliases (yours win) |
post.d/ | After everything | Final tweaks, notifications |
See also: Architecture (boot sequence) Β· Startup (what runs before core)
π Config DirectoryΒΆ
All user configuration lives under a single XDG-compliant directory:
Override the location by setting ZSHAND_CONFIG_DIR in your environment before the shell starts (e.g., in ~/.zshenv).
π Directory structureΒΆ
~/.config/zshand/
βββ π env.zsh Environment variables (auto-exported)
βββ π secrets.zsh API keys and tokens (chmod 600)
βββ π aliases.zsh Custom aliases (loaded after framework)
βββ π€οΈ path.txt Additional PATH entries (one per line)
βββ βοΈ config.toml Framework behavior (see reference below)
βββ π¦ packages.toml Package lists, tools, deactivation (see below)
βββ β¨οΈ keybindings.toml Widget keybindings (optional; or use [keybindings] in config.toml)
βββ π bin/ Personal scripts (added to $PATH)
βββ π οΈ functions/ Custom zsh functions
βββ β¨οΈ widgets/ Custom ZLE widgets
βββ πͺ¨ hooks/ Custom integration hooks
βββ βοΈ rc.d/ Core overrides (same basename = replace)
βββ π init.d/ Pre-initialization scripts
βββ π post.d/ Post-initialization scripts
βββ π plugins/ OMZ/P10k plugins (NN-name.zsh, optional plugins.txt)
First-time setup
Run zsh setup.zsh from the framework repo to create this structure with template files, or copy from samples/user-config/. Set ZSHAND_CONFIG_DIR (e.g. in ~/.zshenv) to use a different path.
π Load OrderΒΆ
Understanding when each file loads helps you decide where to put things. Note: config.toml is read by core/02_vars before it sources env.zsh and secrets.zsh, so TOML values can be overridden by those files.
ββ Shell starts ββββββββββββββββββββββββββββββββββββββββββββββ
β 1. π init.d/*.zsh Early overrides (before core) β
β 2. βοΈ config.toml Read by 02_vars (before env) β
β 3. π¦ packages.toml Resolve packages + deactivation β
β 4. π env.zsh Environment variables β
β 5. π secrets.zsh API keys (auto-exported) β
β 6. π€οΈ path.txt + bin/ PATH configuration β
β 7. βοΈ rc.d/*.zsh Core overrides (merged with core)β
β 8. βοΈ Framework core 02_vars β 04_path β ... β 90 β
β 9. πͺ¨ Framework hooks Tool integrations β
β 10. π οΈ functions/ Your custom functions β
β 11. β¨οΈ widgets/ Your custom widgets β
β 12. πͺ¨ hooks/ Your custom hooks β
β 13. π aliases.zsh Your aliases (can override) β
β 14. π post.d/*.zsh Final tweaks (after everything) β
ββ Shell ready βββββββββββββββββββββββββββββββββββββββββββββββ
π¦ Package Configuration (packages.toml)ΒΆ
Controls which packages, tools, and plugins are installed and active. Uses a 4-level cascade β framework defaults are always present; you only need to override what you want to change.
Quick examplesΒΆ
# ~/.config/zshand/packages.toml
# Disable a tool you don't want
[packages.defaults]
atuin = false # Skips install, sets ZSHAND_ATUIN_IS_DEACTIVATED=1
neovim = false # Uses vim instead
# Add extra packages for your OS
[packages.ubuntu]
htop = true
glances = true
# Add tools to mise
[tools.defaults]
deno = "latest"
bun = "latest"
# Override preferences
[preferences]
js_runtime = "bun"
How deactivation worksΒΆ
When a package is set to false:
- It is not installed by the installer
ZSHAND_<NAME>_IS_DEACTIVATED=1is exported at startup- Related hooks are skipped (e.g.,
hooks/02_atuin.zshchecks the env var) - Related aliases fall back (e.g.,
batβcat,ezaβls) - Related widgets are unbound (e.g., fzf pickers)
Available sectionsΒΆ
| Section | Purpose |
|---|---|
[packages.defaults] / [packages.<os>] | System packages (apt/brew/pacman) |
[tools.defaults] / [tools.<os>] | Mise-managed tools (node, go, rust, uv) |
[cargo.defaults] | Cargo crates |
[npm.defaults] | npm/pnpm global packages |
[pip.defaults] | Python packages |
[plugins.defaults] | Zsh plugin git URLs |
[gui.defaults] / [gui.<os>] | GUI applications |
[flatpak.defaults] / [flatpak.<os>] | Flatpak apps |
[aliases.<os>] | OS-specific package name mappings |
[preferences] | Runtime preferences (js_runtime, python_installer, etc.) |
[settings] | Auto-install, logging settings |
[critical] | Graceful degradation actions for critical tools |
[blacklist] | Packages that should trigger warnings if installed |
[verify] | Post-install command existence checks |
[updates] | Update frequency per category |
π See
samples/user-config/packages.tomlfor the full reference with all options documented.
π Environment Variables (env.zsh)ΒΆ
Machine-specific environment variables. This file is sourced with set -a (auto-export), so every variable is automatically exported.
# env.zsh β Machine-specific environment variables
EDITOR="nvim"
BROWSER="firefox"
MACHINE_ID="workstation"
# Framework controls
ZSHAND_DEBUG=0
ZSHAND_LAZY_LOAD=1
ZSHAND_AUTO_FULL=1
# Tool-specific
DOCKER_HOST="unix:///run/user/1000/docker.sock"
π¬ The framework also writes
ZSHAND="/path/to/zshand"into this file during setup. Don't remove it.
π Secrets (secrets.zsh)ΒΆ
API keys, tokens, and sensitive credentials. Automatically secured with chmod 600 during setup. Never commit this file.
# secrets.zsh β Sensitive credentials (chmod 600)
GITHUB_TOKEN="ghp_xxxxxxxxxxxx"
OPENAI_API_KEY="sk-xxxxxxxxxxxx"
AWS_ACCESS_KEY_ID="AKIAXXXXXXXX"
AWS_SECRET_ACCESS_KEY="xxxxxxxxxxxxxxxx"
CLOUDFLARE_API_TOKEN="xxxxxxxxxxxxxxxx"
Security
This file is protected by chmod 600 (owner-read-only). The framework re-enforces permissions on every startup via core/06_engine.zsh. Gitleaks scans also check that secrets never leak into the repo.
π Aliases (aliases.zsh)ΒΆ
Custom aliases loaded after framework aliases, so they can override:
# aliases.zsh β Custom aliases
alias ll='eza -lahF --icons'
alias gst='git status'
alias dc='docker compose'
alias k='kubectl'
alias tf='terraform'
# Override a framework alias
alias ls='eza --icons --group-directories-first'
π€οΈ PATH (path.txt)ΒΆ
Additional directories to prepend to $PATH, one per line. Lines starting with # are ignored.
# path.txt β Additional PATH entries
/opt/custom/bin
~/.cargo/bin
~/.npm-global/bin
~/.local/share/mise/shims
Your bin/ directory is also automatically added to $PATH.
βοΈ Framework settings (config.toml)ΒΆ
config.toml is read very early (before env.zsh). Each section.key is exported as ZSHAND_SECTION_KEY (e.g. [performance].perf_budget β ZSHAND_PERF_BUDGET). Booleans become 1/0; ~ in paths is expanded.
Main sections (reference)ΒΆ
| Section | Key examples | Purpose |
|---|---|---|
| general | machine_id, theme_config, omz_theme, prezto_theme | Machine ID, P10k path, theme overrides |
| paths | log_dir, cache_dir, comp_cache | Override log/cache dirs |
| logging | json, quiet | JSON logs, suppress hook warnings |
| performance | perf_budget, auto_recompile, health_check, auto_mode | Per-file budget, auto-rebuild, health check, background zprime |
| lazy_loading | defer_compinit, lazy_load | Defer compinit; defer non-critical hooks |
| notices | show_recompile, show_perf, show_p10k_warning | Stale/perf/P10k messages |
| quiet | prime, check, sync, docs | Suppress zprime/zcheck/doc output |
| network | monitor, check_interval | Atuin sync on connectivity |
| clipboard | max_lines, log | cplast limit; clipboard logging |
| ai | explain_lines | aidebug context limit |
| errors | pre_context, post_context, keywords | aidebug/cplast error detection |
| debug | debug, timing_detail | Verbose init; per-file timing |
| optimizations | optimize, defer_mise, defer_gitid | Startup speed tweaks (advanced) |
| tests | (env-only) | E2E timeouts; set via env, not TOML |
Full commented example: samples/user-config/config.toml. Changes take effect on next shell (no rebuild needed).
Minimal exampleΒΆ
# config.toml
[general]
theme_config = "~/.p10k.zsh"
[performance]
perf_budget = 100
auto_mode = true
[lazy_loading]
lazy_load = true
π Pre-Initialization (init.d/)ΒΆ
Scripts that run before the framework loads. Use this for:
- Machine-specific overrides that must be set early
- Environment variables that affect framework behavior
- Workarounds that need to run before core logic
# init.d/00_machine_overrides.zsh
export ZSHAND_LAZY_LOAD=1
export ZSHAND_AUTO_FULL=1
# init.d/10_work_vpn.zsh
[[ -f /etc/vpn-active ]] && export HTTP_PROXY="http://proxy:8080"
Naming: Use NN_ prefix for load order (00 = first, 99 = last).
π Post-Initialization (post.d/)ΒΆ
Scripts that run after everything else. Use this for:
- Final overrides that need all framework functions available
- Customizations that depend on hooks being loaded
- Prompt tweaks that should be the absolute last thing
# post.d/00_final_env.zsh
# Set variables that depend on framework functions
export MY_PROJECT_DIR="$(git rev-parse --show-toplevel 2>/dev/null)"
# post.d/99_prompt_tweaks.zsh
# Final prompt customization (after P10k loads)
typeset -g POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=(dir vcs)
βοΈ Core Overrides (rc.d/)ΒΆ
Replace any framework core file by placing a file with the same basename in rc.d/. The framework merges both directories and user files win:
Framework: $ZSHAND/core/14_aliases.zsh β default
User: ~/.config/zshand/rc.d/14_aliases.zsh β loaded INSTEAD
Framework: $ZSHAND/core/10_options.zsh β default
User: ~/.config/zshand/rc.d/11_my_options.zsh β loaded ALONGSIDE (odd number)
Rules:
- Same basename β replaces framework file entirely
- Different basename β adds to load sequence (use odd numbers)
Example β add PATH entries between 04_path and 06_engine:
π οΈ Custom Functions (functions/)ΒΆ
Drop .zsh files here to add your own functions:
These are loaded after framework functions and can override them.
β¨οΈ Keybindings (keybindings.toml or config.toml)ΒΆ
Widget keybindings can live in a dedicated file keybindings.toml or in a [keybindings] section of config.toml. Loaded by startup/17_az_bindkey_from_toml.zsh after default bindings.
Format: "key" = "widget_name". Key names use lowercase with hyphens: ctrl+g, alt+m, f12. Empty value unbinds the key.
# keybindings.toml (or [keybindings] in config.toml)
[keybindings]
"ctrl+g" = "aicmit" # AI commit
"ctrl+d" = "aidebug" # AI debug
"ctrl+p" = "pastem" # Smart paste
"ctrl+o" = "copym" # Copy line
"alt+m" = "manf" # Man page search
See Widgets for built-in widget names. After changing keybindings, reload with exec zsh (no zr needed).
β¨οΈ Custom widgets (widgets/)ΒΆ
ZLE widgets for keyboard shortcuts:
Bind in keybindings.toml or [keybindings] in config.toml:
πͺ¨ Custom hooks (hooks/)ΒΆ
Integration hooks for tools not covered by the framework:
Use numbering (e.g. 50_) to control order; same basename as a framework hook replaces it. See Hooks.
π Plugins (plugins/ and plugins.txt)ΒΆ
The framework loads plugins from ~/.config/zshand/plugins/. Files named NN-name.zsh (e.g. 50-zsh-autosuggestions.zsh) are loaded in numeric order and compiled into the full bundle. Syntax highlighting must load last β use prefix 99.
- Bundle P10k config: Copy
~/.p10k.zshtoplugins/05-p10k-theme.zshfor faster startup; runzrafter changes. - Add a plugin: Copy plugin source into
plugins/NN-plugin-name.zshor add a wrapper thatsources the system path. - Optional
plugins.txt: List plugins with prefixes: none = immediate,~= lazy (after first prompt),+= deferred;-= disabled. Ifplugins.txtexists, only listed plugins load.
Full details and OS-specific paths: samples/user-config/plugins/README.md.
π Personal scripts (bin/)ΒΆ
Executable scripts placed here are automatically added to $PATH:
Make them executable: chmod +x ~/.config/zshand/bin/my_script
π Complete Override (zshrc.zsh)ΒΆ
For total control, create ~/.config/zshand/zshrc.zsh. This completely replaces the framework β nothing else loads:
# zshrc.zsh β Complete framework replacement
# WARNING: This disables ALL framework features
source ~/.config/zshand/env.zsh
source ~/.config/zshand/aliases.zsh
# ... your custom setup
Nuclear option
This is a full replacement β no framework code runs at all. Only use this if you need complete control over your shell setup.
π Key environment variablesΒΆ
Set these before the framework loads (e.g. in ~/.zshenv or env.zsh). Many can also be set via config.toml (see table above).
| Variable | Default | Purpose |
|---|---|---|
ZSHAND_CONFIG_DIR | ~/.config/zshand | Your config directory |
ZSHAND_CACHE_DIR | ~/.cache/zshand | Compiled bundles and cache |
ZSHAND_LOG_DIR | ~/logs/zshand | Log files |
ZSHAND_DEV_MODE | 0 | Development mode (no bundles) |
ZSHAND_AUTO_FULL | 0 | Auto-compile full bundle |
ZSHAND_DEBUG | 0 | Verbose debug output |
ZSHAND_LAZY_LOAD | 0 | Defer non-critical hooks |
ZSHAND_SAFE_MODE | 0 | Emergency recovery |
ZSHAND_PERF_BUDGET | 100 | Max ms per file |
β TroubleshootingΒΆ
| Issue | What to try |
|---|---|
| Config not loading | Ensure ZSHAND_CONFIG_DIR points to your config dir (set in ~/.zshenv if needed). Run echo $ZSHAND_CONFIG_DIR in a new shell. |
| Keybinding has no effect | Check widget name (e.g. pastem not paste). Reload with exec zsh. If you added a new widget, run zr so itβs in the bundle. |
| Changes to .zsh files not applied | Run zr (or zfull if using full bundle) then exec zsh. Framework loads compiled bundles, not raw source in production. |
| "Bundles stale" at startup | Run zr to rebuild; then exec zsh. |
| Secrets or env not taking effect | env.zsh and secrets.zsh are sourced early; ensure no syntax errors. Use ZSHAND_DEBUG=1 exec zsh to see load order. |
See Troubleshooting and Safe Mode for more.
π Related documentsΒΆ
| Document | Purpose |
|---|---|
| ποΈ Architecture | Override system, load order, directory layout |
| βοΈ Core | What rc.d/ overrides and merge order |
| β¨οΈ Widgets | Built-in widgets and keybinding names |
| πͺ¨ Hooks | Framework hooks and how to add your own |
| π¨ Style Guide | Conventions for custom code |
| π Header Standard | Documenting your custom files |
| π₯ Installers | Platform setup and sample templates |
| π§ Troubleshooting | Common problems and fixes |
| π Documentation Index | Full doc nav |