In Lesson 2 you used built-in variables like .chezmoi.hostname to detect which machine you're on. That works for simple cases, but what happens when you want to define your own categories — like "this is a work laptop" or "install these packages on home machines only"? That's what .chezmoidata files are for.
Chezmoi merges data from multiple sources into a single template data dictionary. Your custom data files sit in the source state and inject variables that templates can reference — without touching the config file.
Each source overrides the previous one. Dictionaries are deep-merged, but lists and scalar values are replaced entirely. This means your .chezmoidata.toml can override anything set in the config file's [data] section, and files in the .chezmoidata/ directory override the single file.
The config file (~/.config/chezmoi/chezmoi.toml) is machine-local — it doesn't get committed to your repo. Data files live inside the source state, so they're version-controlled and shared across machines. Use the config for machine-specific secrets (API keys, passwords). Use data files for shared structured data (package lists, feature flags, categories).
Create a .chezmoidata.toml file in the root of your source state. You can use TOML, YAML, or JSON — pick whichever you prefer. TOML is the most common choice:
# ~/.local/share/chezmoi/.chezmoidata.toml
machine_type = "work"
[features]
install_docker = true
install_node = true
use_1password = false
[packages]
common = ["git", "ripgrep", "fd", "fzf", "jq", "bat"]
work = ["awscli", "terraform", "kubectl"]
home = ["ffmpeg", "yt-dlp", "imagemagick"]
Every key becomes a top-level template variable. After adding this file, you can reference .machine_type, .features.install_docker, and .packages.common in any template.
If you prefer YAML, create .chezmoidata.yaml instead:
# ~/.local/share/chezmoi/.chezmoidata.yaml
machine_type: work
features:
install_docker: true
install_node: true
use_1password: false
packages:
common:
- git
- ripgrep
- fd
- fzf
- jq
- bat
work:
- awscli
- terraform
- kubectl
home:
- ffmpeg
- yt-dlp
- imagemagick
.chezmoidata files are loaded before the template engine starts. You cannot use {{ }} template syntax inside them. If you need dynamic data computed at apply-time, put it in the [data] section of your config file template (.chezmoi.toml.tmpl) instead.
Here's a practical example — a .zshrc template that conditionally loads tools based on machine_type:
# ~/.local/share/chezmoi/dot_zshrc.tmpl
# Common aliases
alias ll="ls -la"
alias gs="git status"
{{ if eq .machine_type "work" -}}
# Work-specific: AWS and Kubernetes
export AWS_PROFILE=default
alias k="kubectl"
alias tf="terraform"
source <(kubectl completion zsh)
{{ end -}}
{{ if eq .machine_type "home" -}}
# Home-specific: media tools
alias yt="yt-dlp"
alias resize="magick mogrify -resize"
{{ end -}}
# Packages this machine should have installed:
# {{ if eq .machine_type "work" }}{{ range .packages.work }}# {{ . }}
# {{ end }}{{ else }}{{ range .packages.home }}# {{ . }}
# {{ end }}{{ end }}
You can also iterate over lists. Here's a Brewfile template that installs different packages per machine type:
# ~/.local/share/chezmoi/dot_Brewfile.tmpl
{{ range .packages.common -}}
brew "{{ . }}"
{{ end -}}
{{ if eq .machine_type "work" -}}
{{ range .packages.work -}}
brew "{{ . }}"
{{ end -}}
{{ end -}}
{{ if eq .machine_type "home" -}}
{{ range .packages.home -}}
brew "{{ . }}"
{{ end -}}
{{ end -}}
Open your source state and create a .chezmoidata.toml:
chezmoi cd
Create the file with your machine classification:
cat > .chezmoidata.toml << 'EOF'
# Machine classification
machine_type = "home"
[features]
install_docker = true
use_1password = false
EOF
Then return to your shell:
exit
Run chezmoi data to see the merged data dictionary. Your custom variables should appear alongside the built-in .chezmoi object:
chezmoi data | grep -A 2 machine_type
You should see:
"machine_type": "home",
To see everything (it's a lot of output):
chezmoi data | less
You'll see your machine_type and features keys at the top level, alongside chezmoi (the built-in data object with hostname, OS, architecture, etc.).
Create a simple template that uses your new variable. Let's make a ~/.machine-info file that displays what kind of machine this is:
chezmoi cd
cat > dot_machine-info.tmpl << 'EOF'
Machine: {{ .chezmoi.hostname }}
Type: {{ .machine_type }}
Docker: {{ if .features.install_docker }}yes{{ else }}no{{ end }}
1Password: {{ if .features.use_1password }}yes{{ else }}no{{ end }}
EOF
exit
chezmoi cat ~/.machine-info
This shows what chezmoi would write without actually writing it. You should see your hostname and "home" filled in. If it looks right:
chezmoi apply ~/.machine-info
chezmoi cd
git add .chezmoidata.toml dot_machine-info.tmpl
git commit -m "feat: add custom data file and machine-info template"
git push
exit
For more complex setups, you can split your data across multiple files using a .chezmoidata/ directory instead of (or in addition to) a single file:
~/.local/share/chezmoi/
├── .chezmoidata.toml # base data
├── .chezmoidata/
│ ├── packages.toml # package lists
│ └── work-tools.yaml # work-specific config
├── dot_zshrc.tmpl
└── ...
Files inside .chezmoidata/ are merged in alphabetical order. This is useful when you want to organize data by concern — keep package lists separate from feature flags, for example.
Dictionaries (maps/objects) are deep-merged — keys from later files are added to or override keys from earlier files. But lists (arrays) are replaced entirely, not appended. If packages.toml defines packages.common and work-tools.yaml also defines packages.common, the YAML version wins completely.
| Concept | What it does |
|---|---|
.chezmoidata.toml | Defines custom template variables in the source state |
.chezmoidata/ directory | Splits data across multiple files, merged alphabetically |
chezmoi data | Shows the full merged data dictionary (built-in + custom) |
| Merge hierarchy | config [data] < .chezmoidata file < .chezmoidata/ directory |
| Lists vs dicts | Dicts deep-merge; lists and scalars are replaced entirely |
.chezmoidata.toml live?~/.local/share/chezmoi/. They're version-controlled alongside your templates..chezmoidata.toml lives in the source state root — ~/.local/share/chezmoi/.chezmoidata.toml. It's committed to your repo alongside your templates.[data] section and .chezmoidata.toml define machine_type, which value wins?.chezmoidata file < .chezmoidata/ directory. Later sources override earlier ones for scalar values..chezmoidata.toml overrides the config file's [data] section. The order is: config data < .chezmoidata file < .chezmoidata/ directory.{{ .chezmoi.hostname }} inside .chezmoidata.toml?[data] section of .chezmoi.toml.tmpl instead..chezmoi.toml.tmpl) with its [data] section.You now have custom per-machine variables driving your templates. But dotfiles are only part of machine setup — you also need to install packages, configure macOS defaults, and run setup scripts. In Lesson 4: Scripts & Brew Bundles, you'll learn how chezmoi runs scripts on apply and how to declaratively manage your Homebrew packages.
chezmoi Reference: .chezmoidata.<format> — The official docs for data files, including multi-file merge behavior and format support. ~3 minute read.
chezmoi data.