Creating a Custom Zsh Completion Function with compdef and _arguments
Learn how to add tab‑completion for your own commands in Zsh by defining a completion function, linking it with compdef, and using the _arguments helper.
01 Jan 2026, 23:34 UTC

Useful answer: add completion to any command in a few lines
If you want Zsh to suggest flags or arguments when you press Tab after a command you wrote, define a completion function, register it with compdef, and let the _arguments helper describe the options. The rest of the completion system (fpath, compinit) handles loading and invoking your function automatically.
How the mechanism works
Zsh’s programmable completion system (compsys) searches directories listed in the fpath array for files whose name starts with an underscore (_). When you type a command and press Tab, the system looks for a file named _<command>. If it finds one, it sources the file and calls the function defined inside.
The compdef builtin is a convenience wrapper that creates (or updates) that underscore‑prefixed file for you and tells the completion system which function to use for a given command name. Inside the function you typically call _arguments, which declaratively specifies flags, their arguments, and positional parameters.
Worked example: completion for a dummy tool
Suppose you have a command mytool that accepts:
--input <file>– required input file--output <file>– optional output file- a final positional argument that is a directory
Add the following to your ~/.zshrc (or source it in a test session):
# 1. Ensure the completion system is initialized
if [[ ! -o ksharrays ]]; then
autoload -Uz compinit && compinit
fi
# 2. Define the completion function
_mytool() {
# _arguments syntax:
# option-description variable-action
# * - default action for unmatched words
_arguments \
'--input[Input file]:_files' \
'--output[Output file]:_files' \
'*:directory:_directories'
}
# 3. Register the function for the command "mytool"
compdef _mytool mytool
Explanation of the _arguments line:
'--input[Input file]:_files'– when the token--inputis seen, the next word is completed as a file (_files). The text in brackets appears as the description in the completion menu.'--output[Output file]:_files'– similar for the optional output flag.'*:directory:_directories'– the*matches any remaining word; it is completed as a directory (_directories).
After reloading your shell (or sourcing the snippet), try:
$ mytool --Tab
You should see the two flags listed. Typing --input then Tab offers files in the current directory, and after providing both flags the final Tab suggests directories.
Limits and common mistakes
1. Forgetting to initialize compsys
If you call compdef before compinit runs, the function is stored but the completion engine never looks it up. Always ensure autoload -Uz compinit && compinit executes first (typically early in ~/.zshrc).
2. Overwriting system completions carelessly
Re‑defining _git or _ssh can hide the rich default logic. If you must override a system command, copy the original completion file (which _git) and extend it rather than replace it entirely.
3. Performing heavy work inside the completion function
The completion function runs on every Tab press. Avoid costly operations like network calls or large file scans. If you need dynamic data, cache results or limit the work to the current context (e.g., use _files -/ to restrict to directories).
4. Missing the trailing newline or incorrect quoting
Each argument to _arguments must be a single quoted string. Forgetting the quotes or adding extra spaces can cause the parser to treat parts of the description as separate actions, leading to no completions or errors.
5. Not returning a non‑zero status when nothing matches
Completion functions should return 1 when they cannot generate any matches; otherwise Zsh may fall back to default file completion unexpectedly. The _arguments helper handles this automatically, but if you add custom logic, remember to return 1 when appropriate.
Verification steps
- Start a fresh Zsh session (or source the snippet).
- Type
mytool --and press Tab – you should see--inputand--output. - Type
mytool --inputthen Tab – file names appear. - After supplying both flags, type a space and press Tab – directory names appear.
- If nothing shows up, check
echo $fpathfor the directories searched and ensure_mytoolexists in one of them (you can locate it withwhich _mytool).
When to use this approach
This pattern is ideal for:
- Personal scripts or small utilities where you want a pleasant CLI experience.
- Prototyping a command before committing to a full‑featured completion script.
- Situations where the completion logic is mostly static (flags, known file types).
For highly dynamic completions (e.g., querying an API for each token), consider using the _arguments -S flag to enable stateful parsing or implement your own lookup and cache the results outside the completion function.
By following the steps above you can add reliable, low‑overhead tab‑completion to any Zsh command, making your shell workflow faster and less error‑prone.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.