Skip to main content

Understand how scripts work

chezmoi supports scripts that execute when you run chezmoi apply. Scripts are files in the source directory with the run_ prefix, executed in alphabetical order.

Script types

run_

Execute every time you run chezmoi apply

run_onchange_

Execute only when script contents have changed

run_once_

Execute once per unique content version
All scripts should be idempotent (safe to run multiple times). Scripts break chezmoi’s declarative approach and should be used sparingly.

Script timing

Scripts normally run while chezmoi updates your dotfiles:
a.txt → run_b.sh → c.txt
Use before_ or after_ attributes to control timing:
run_once_before_install-password-manager.sh

Script requirements

1

Create manually

chezmoi cd
touch run_install-packages.sh
chmod +x run_install-packages.sh
No need to set executable bit - chezmoi handles it.
2

Include shebang or use binary

#!/bin/bash
echo "Installing packages..."
Scripts must include a #! line or be an executable binary.
3

Template support

run_install.sh.tmpl
{{ if eq .chezmoi.os "linux" -}}
#!/bin/sh
sudo apt install ripgrep
{{ else if eq .chezmoi.os "darwin" -}}
#!/bin/sh
brew install ripgrep
{{ end -}}
Scripts with .tmpl suffix are templated. If template resolves to whitespace, script won’t execute.
Scripts in .chezmoiscripts/ directory are executed without creating a corresponding directory in the target state.

Set environment variables

Configure extra environment variables for scripts in your config:
~/.config/chezmoi/chezmoi.toml
[scriptEnv]
    MY_VAR = "my_value"
    API_KEY = "{{ onepassword "api-key" }}"
chezmoi automatically sets:
  • CHEZMOI=1
  • CHEZMOI_OS (e.g., linux, darwin)
  • CHEZMOI_ARCH (e.g., amd64, arm64)
  • Other template data as CHEZMOI_* variables

Don’t show scripts in chezmoi diff/chezmoi status

~/.config/chezmoi/chezmoi.toml
[diff]
    exclude = ["scripts"]
Scripts won’t appear in chezmoi diff output.

Install packages with scripts

1

Create installation script

chezmoi cd
touch run_onchange_install-packages.sh
2

Add package installation commands

#!/bin/sh
sudo apt install ripgrep
3

Apply changes

chezmoi apply
Script runs on first apply. With run_onchange_, it won’t run again unless contents change.
Name your template run_onchange_install-packages.sh.tmpl to install platform-specific packages automatically.

Run a script when another file changes

Use content checksums to trigger scripts when other files change:
#!/bin/bash

# dconf.ini hash: {{ include "dconf.ini" | sha256sum }}
dconf load / < {{ joinPath .chezmoi.sourceDir "dconf.ini" | quote }}
How it works:
  1. Template includes SHA256 hash of dconf.ini in a comment
  2. When dconf.ini changes, the hash changes
  3. Changed hash means changed script content
  4. chezmoi reruns the run_onchange_ script
Add dconf.ini to .chezmoiignore so chezmoi doesn’t create it in your home directory.

Clear the state of all run_onchange_ and run_once_ scripts

chezmoi tracks script execution in persistent state.
chezmoi state delete-bucket --bucket=entryState
All run_onchange_ scripts will run again on next chezmoi apply.
Clearing state causes scripts to run again. Ensure your scripts are idempotent before doing this.

Real-world examples

{{ if eq .chezmoi.os "darwin" -}}
#!/bin/bash

if ! command -v brew &> /dev/null; then
    echo "Installing Homebrew..."
    /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
fi
{{- end }}

Build docs developers (and LLMs) love