Making Zsh Tab‑Completion Work for Your Own Scripts
Learn how to enable Zsh’s programmable completion for your own scripts by writing a simple completion function, linking it with compdef, and verifying the result in a fresh shell.
12 Feb 2026, 22:51 UTC

Problem: Your script ignores Tab
You have written a handy command‑line tool, mytool, that accepts options like --input FILE and --mode (fast|slow). When you type mytool -- and press Tab, Zsh falls back to filename completion instead of showing the expected arguments. This breaks the flow and forces you to remember the exact spelling of each flag.
Thesis: Enable programmable completion with compinit and bind a simple completion function
Zsh’s completion system is turned on by autoloading and initializing compinit. Once active, it reads completion definitions from directories listed in $fpath. You can add your own definition without touching the core code by writing a shell function and associating it with the command via compdef.
1. Ensure the completion system is loaded
Start a fresh Zsh session (or source your .zshrc) and run:
# Run once per session; typically placed in .zshrc
autoload -Uz compinit # load the compinit function
compinit # initialize it
This populates $fpath with the standard completion directories and prepares the zcompdump cache.
2. Write a minimal completion function for mytool
Create a file (e.g., ~/.zfunc/_mytool) that contains the function. The function name must start with an underscore.
# ~/.zfunc/_mytool
# Arguments: $1 = service, $2 = word being completed, $3 = word before cursor
_mytool() {
local -a opts
opts=(
'--input[Specify input file]:_files'
'--mode[Operation mode]:(fast slow)'
'--help[Display help]'
)
_describe 'command' opts
}
# Tell Zsh to use this function for the command 'mytool'
compdef _mytool mytool
Explanation:
_filestriggers filename completion for the argument after--input.- The syntax
(fast slow)offers a fixed set of values for--mode. _describeformats the options for the completion menu.
3. Make the function discoverable
Add the directory containing the function to $fpath before calling compinit. A common place is ~/.zfunc.
# In .zshrc, before compinit fpath+=~/.zfunc autoload -Uz compinit compinitAfter saving the changes, start a new shell or run
source ~/.zshrc.4. Verify the completion works
In the same session, type:
mytool --TabYou should see a list:
--help,--input,--mode. Pressing Tab again cycles through the options if you have enabled menu selection:zstyle ':completion:*' menu selectTrade‑off and limitation
Enabling many completions or using complex
zstylepatterns can increase shell startup latency, especially when$fpathpoints to large directories containing hundreds of files. The completion cache (~/.zcompdump) mitigates this by storing the parsed definitions, but the cache must be regenerated whenever you modify a completion function.Practical way to check the cache:
ls -l ~/.zcompdump # shows timestamp and sizeAfter editing
_mytool, delete the cache and restart the shell to force a rebuild:rm -f ~/.zcompdump exec zsh # or start a new terminalIf a completion function is poorly written (e.g., an infinite loop inside the function), Zsh may appear to hang when you press Tab. Test new completions in a temporary shell (
zsh -f) before adding them to your main configuration.Actionable closing
To get reliable Tab completion for your own scripts:
- Add
fpath+=~/.zfuncbeforecompinitin~/.zshrc.- Write a completion function named
_<command>that uses_describeand helpers like_files.- Link it with
compdef _<command> <command>.- Reload the shell, verify with Tab, and optionally enable
zstyle ':completion:*' menu selectfor a navigable list.- Keep an eye on startup time; if it grows, prune unused directories from
$fpathor rely on the~/.zcompdumpcache.With these steps, your custom commands will behave like built‑ins, offering contextual suggestions that save typing and reduce errors.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.