Not everything in your home directory comes from files you wrote. Fonts, shell plugins, binary tools — these live on the internet and you need them pulled into the right place on every machine. Chezmoi's .chezmoiexternal.toml handles this without cluttering your dotfiles repo with third-party code.
Instead of committing external files into your source state (bloating your repo), you declare where to fetch them from and where to place them. Chezmoi downloads and extracts them at apply time.
The declaration lives in your source state root (alongside dot_zshrc and friends). It's a TOML file where each section maps a target path to a remote URL.
Every entry in .chezmoiexternal.toml needs a type. The two main options:
| Type | Use when | What happens |
|---|---|---|
archive | URL points to a .tar.gz, .zip, or similar | Downloads and extracts into the target directory |
file | URL points to a single raw file | Downloads and places the file at the target path |
Download a single font file from a URL and place it in your fonts directory:
[".local/share/fonts/JetBrainsMono-Regular.ttf"]
type = "file"
url = "https://github.com/JetBrains/JetBrainsMono/raw/master/fonts/ttf/JetBrainsMono-Regular.ttf"
The key (in brackets) is the path relative to your home directory where the file will land.
Pull the entire oh-my-zsh framework from its GitHub archive:
[".oh-my-zsh"]
type = "archive"
url = "https://github.com/ohmyzsh/ohmyzsh/archive/master.tar.gz"
exact = true
stripComponents = 1
refreshPeriod = "168h"
Let's unpack those options:
GitHub archives wrap everything in a top-level directory (like ohmyzsh-master/). Setting stripComponents = 1 removes that wrapper so the contents land directly in the target path. This is the same concept as tar --strip-components=1.
When exact = true, chezmoi ensures the target directory contains only what's in the archive. Any files that exist in the target but aren't in the archive get deleted. Without this, old files from previous versions would accumulate.
Only set exact = true when chezmoi should own the entire directory. If other tools write into that same directory, those files will be deleted on the next chezmoi apply.
By default, chezmoi re-downloads external files on every apply. That's slow and unnecessary for things that rarely change. Set refreshPeriod to cache:
refreshPeriod = "168h" # Re-download after 7 days
refreshPeriod = "720h" # Re-download after 30 days
Chezmoi stores a cache of downloaded externals. If the refresh period hasn't elapsed, it skips the download entirely. You can force a re-download with:
chezmoi apply --refresh-externals
For plugins that track master: 7 days (168h) is reasonable. For pinned releases: 30 days or more. For font files that never change: omit refreshPeriod and they'll only download once.
[".oh-my-zsh/custom/plugins/zsh-autosuggestions"]
type = "archive"
url = "https://github.com/zsh-users/zsh-autosuggestions/archive/master.tar.gz"
exact = true
stripComponents = 1
refreshPeriod = "168h"
[".local/bin/fzf"]
type = "archive-file"
url = "https://github.com/junegunn/fzf/releases/download/v0.55.0/fzf-0.55.0-darwin_arm64.tar.gz"
path = "fzf"
refreshPeriod = "720h"
The archive-file type extracts a single file from within an archive. The path key specifies which file inside the archive to extract.
[".oh-my-zsh/custom/plugins/zsh-syntax-highlighting"]
type = "archive"
url = "https://github.com/zsh-users/zsh-syntax-highlighting/archive/master.tar.gz"
exact = true
stripComponents = 1
refreshPeriod = "168h"
[".oh-my-zsh/custom/plugins/zsh-completions"]
type = "archive"
url = "https://github.com/zsh-users/zsh-completions/archive/master.tar.gz"
exact = true
stripComponents = 1
refreshPeriod = "168h"
Each plugin gets its own section. The pattern is identical — just change the URL and target path.
Open (or create) .chezmoiexternal.toml in your source state root:
chezmoi edit ~/.chezmoiexternal.toml
If the file doesn't exist yet, chezmoi will create it. Alternatively, create it directly:
chezmoi cd
touch .chezmoiexternal.toml
Add this to .chezmoiexternal.toml:
[".oh-my-zsh/custom/plugins/zsh-autosuggestions"]
type = "archive"
url = "https://github.com/zsh-users/zsh-autosuggestions/archive/master.tar.gz"
exact = true
stripComponents = 1
refreshPeriod = "168h"
chezmoi apply
Check that the plugin landed in the right place:
ls ~/.oh-my-zsh/custom/plugins/zsh-autosuggestions/
You should see files like zsh-autosuggestions.zsh, README.md, etc.
chezmoi cd
git add .chezmoiexternal.toml
git commit -m "chore: add zsh-autosuggestions via external archive"
git push
exit
Now any machine that runs chezmoi apply will automatically pull down zsh-autosuggestions without you needing to clone it manually.
| Concept | What it does |
|---|---|
.chezmoiexternal.toml | Declares remote files/archives to pull at apply time |
type = "file" | Downloads a single file to the target path |
type = "archive" | Downloads and extracts an archive into a directory |
stripComponents | Removes N leading path segments from archive entries |
exact = true | Ensures target directory only contains archive contents |
refreshPeriod | Caches downloads to avoid re-fetching on every apply |
--refresh-externals | Forces re-download regardless of cache |
stripComponents = 1 do when extracting a GitHub archive?repo-master/. Setting stripComponents = 1 removes that layer so files land directly in your target path.stripComponents removes leading directory levels from archive paths — just like tar --strip-components. GitHub archives wrap everything in repo-branch/, so stripping 1 level puts files directly in the target.type = "file" instead of type = "archive"?file when the URL is a direct link to a single file — a font, a script, a binary. Use archive when it's a compressed bundle that needs extracting.type = "file" when the URL points directly to a single file (not compressed). For .tar.gz, .zip, or other archives, use type = "archive".exact = true on an external archive and a file exists in the target directory that isn't in the archive?exact = true means chezmoi owns that directory completely. Anything not in the archive gets removed. This keeps plugin directories clean across updates.exact = true, chezmoi ensures the directory matches the archive exactly — extra files get deleted. This is intentional for directories chezmoi should fully own.You can now pull external dependencies declaratively — no manual git clone or curl scripts needed. In Lesson 10: Bootstrap a New Machine, you'll tie everything together: init, templates, scripts, and externals into a single command that sets up a fresh machine from scratch.
chezmoi: Include files from elsewhere — The full reference for .chezmoiexternal.toml including all supported types, options, and advanced patterns like filters and checksums.