Stop Guessing Flags: Building Custom Zsh Completions for Your CLI Tools
Stop guessing CLI flags. Learn how to use Zsh's _arguments utility and fpath to create custom, declarative tab-completions for your internal tools.
21 Jul 2026, 18:32 UTC

The Tab-Completion Gap
You've built a custom CLI tool to automate a workflow, but using it requires constant trips to the --help menu or a mental map of every flag. When a tool grows beyond three or four arguments, the lack of tab-completion isn't just a convenience issue—it's a productivity leak. While basic shell completion handles filenames, it doesn't know that --env should only be followed by production or staging.
The solution is Zsh's programmable completion system (compsys). By defining a completion function, you can transform a blind tab-press into a curated list of valid options, flags, and dynamic values.
How Zsh Locates Completion Logic
Zsh doesn't hardcode completions; it looks for functions in directories listed in the fpath (function path) array. When you type a command and hit Tab, Zsh searches fpath for a file named _commandname. If found, it executes that function to determine what to display.
To implement this, you must ensure your custom completion directory is added to fpath before calling compinit in your .zshrc. If compinit runs first, Zsh may not recognize your new definitions until the shell is restarted.
Declarative Completion with _arguments
For most CLI tools, you don't need to write complex logic. The _arguments utility allows you to define your CLI's interface declaratively. It handles the heavy lifting of parsing the current command line and suggesting the next logical piece of input.
The syntax for _arguments follows a specific pattern: 'flag:description:action'. The action can be a static list of values, a reference to another completion function, or a message to the user.
Worked Example: A Deployment Tool
Imagine a tool called deploy-app with the following requirements:
- A
--envflag that acceptsdev,staging, orprod. - A
--regionflag that acceptsus-east-1oreu-west-1. - A required positional argument for the
service-name.
Create a file named _deploy-app (note the leading underscore) and place it in a directory within your fpath:
#compdef deploy-app
_deploy-app() {
_arguments '--env[Specify target environment]:env:(_values env dev staging prod)' '--region[Specify cloud region]:region:(_values region us-east-1 eu-west-1)' '1:Service Name:service:()'
}
Configuration and Setup:
Run these commands in your terminal or add them to .zshrc to activate the function:
# Add the directory containing _deploy-app to fpath
fpath=(~/my-zsh-completions $fpath)
# Initialize the completion system
autoload -Uz compinit
compinit
Verification:
Type deploy-app --env [TAB]. You should see dev, staging, and prod. Type deploy-app [TAB] to see the service name prompt. To debug a failing completion, run zsh -xv and trigger the tab-completion to trace exactly where the function is exiting.
Performance Trade-offs and Limits
Programmable completion is powerful, but it runs every time you hit Tab. If your completion function calls an external API or executes a heavy shell script to fetch a list of IDs, you will notice a perceptible lag (latency) in your shell. For dynamic lists, it is better to cache the results in a temporary file or use a fast local lookup.
Additionally, the _arguments syntax is strict. A missing colon or a misplaced quote can cause the completion to fail silently, returning no results without an error message. Always verify your syntax using the zsh -xv trace method if the suggestions don't appear.
Closing Action
Start by mapping your most-used internal tools. If you find yourself copy-pasting flags from a README more than twice a day, spend ten minutes writing a _arguments definition. It reduces cognitive load and prevents deployment errors caused by typos in environment flags.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.