Completion is function-based: on Tab zsh finds a completion function matching the command name, and it must exist. Zsh reads no --help.

Minimum setup:

autoload -Uz compinit
compinit

zstyle ':completion:*' menu select
zstyle ':completion:*' matcher-list 'm:{a-z}={A-Z}'

setopt AUTO_MENU
setopt COMPLETE_IN_WORD

compinit loads completion and builds a dump file, ~/.zcompdump. Rebuild it if changes do not appear, or zsh warns about an insecure completion directory:

rm -f ~/.zcompdump*
compinit

Git completion Link to this section

Git support is zsh’s own _git function: once compinit has run, these complete on Tab with refs, remotes and branches:

git checkout <TAB>
git switch <TAB>
git merge <TAB>
git rebase <TAB>
git push origin <TAB>

Oh My Zsh enables it; not required.

Who ships the script Link to this section

A tool’s completion file is named after the command — _myapp — on $fpath, usually ~/.zsh/completions/ or the package manager’s site-functions directory. Modern CLIs generate it:

myapp completion zsh > ~/.zsh/completions/_myapp
compinit

Cobra (Go), clap (Rust), yargs or oclif (Node) emit one; writing your own is the fallback for internal tools.

Completion for an alias Link to this section

Shell alias for git — alias g=git — must reuse git’s completion:

alias g=git
compdef g=git

Keep completealiases off, or zsh expands the alias before completion runs and the binding never matches:

unsetopt completealiases

Git’s own aliases — co = checkout — are handled by the official script via git help --aliases-for-completion.

Git aliases that shell out — starting with ! — are opaque shell commands; attach completion to a shell function:

wa() { command git wa "$@"; }
compdef _git-checkout wa

Branch completion in your own command Link to this section

autoload -Uz compinit
compinit

_mycmd() {
  local -a branches

  if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
    branches=(${(f)"$(git for-each-ref --format='%(refname:short)' refs/heads 2>/dev/null)"})
    _arguments '1:branch:compadd -a branches'
  else
    _message 'not in a git repository'
  fi
}

compdef _mycmd mycmd

Add refs/remotes to for-each-ref for remote branches; scope to one subcommand via $words[2]. A branch argument followed by more:

_arguments \
  '1:branch:_git-branch' \
  '*::args:_files'

Helpers by type: _git-branch/_git-switch refs, _files paths, _git-remote remotes.

compdef does nothing until compinit has run.

References Link to this section