Zsh Without the Bloat: A Minimal .zshrc
Somewhere between your first Mac and your second job, configuring Zsh stopped being something you do and became something you install. Oh My Zsh has been the default answer since 2009: three hundred plugins, a hundred and forty themes, and — if you’re not careful — a shell startup time you could measure with a sundial.
Here’s what the install script doesn’t mention: the useful core of all that is about forty
lines of configuration. This guide builds a fast, minimal .zshrc that keeps the parts people
actually love — history that works, completion that doesn’t need a manual, the grey ghost
text — and drops the rest. No framework, no magic, and a prompt that appears before your
finger has left the Enter key.
First, measure
Section titled “First, measure”Before touching anything, get a baseline:
time zsh -i -c exitRun it a few times and take the best. This starts an interactive zsh, exits immediately, and tells you what your current setup costs per shell. Note the number — you’ll want it for the smug comparison at the end.
What Oh My Zsh actually does for you
Section titled “What Oh My Zsh actually does for you”Credit where due: Oh My Zsh isn’t a scam. It’s a bundle, and bundles are how you end up paying for channels you don’t watch. Unpack it and you get:
- Sensible defaults for history and completion — which we’re about to set directly, in six lines.
- A prompt theme engine — replaced, better and cross-shell, by Starship.
- Plugin loading — which, for the two plugins that matter, is two
sourcelines. - The git plugin’s aliases — genuinely beloved, and the full list is public. Steal your favourites; we’ll get to that.
- Three hundred other plugins and 140 themes you will never enable.
The real cost isn’t even the startup time — it’s the opacity. Most framework users can’t say what their shell is actually doing. The config below is one file you understand completely.
A .zshrc worth stealing
Section titled “A .zshrc worth stealing”This is the whole thing. Create or replace ~/.zshrc:
# ~/.zshrc — the whole thing
## HistoryHISTFILE=~/.zsh_historyHISTSIZE=50000SAVEHIST=50000setopt SHARE_HISTORY # one history shared across all sessionssetopt HIST_IGNORE_DUPS # no consecutive repeatssetopt HIST_IGNORE_SPACE # " secret-command" never touches historysetopt HIST_REDUCE_BLANKS
## Completion: rebuild the cache daily, not once per shellsetopt EXTENDED_GLOB # the glob qualifier below is useless without itautoload -Uz compinitif [[ -n ~/.zcompdump(#qN.mh+24) ]]; then compinitelse compinit -Cfizstyle ':completion:*' menu select # arrow through completionszstyle ':completion:*' matcher-list 'm:{a-z}={A-Za-z}' # case-insensitive
## Quality of lifesetopt AUTO_CD # type a directory name to cd to itsetopt INTERACTIVE_COMMENTS # allow # comments at the promptsetopt NO_BEEPbindkey -e # readline-style keys, whatever $EDITOR says
## Toolseval "$(starship init zsh)"eval "$(zoxide init zsh)"eval "$(fzf --zsh)"
## Plugins (install below; keep syntax-highlighting last)source ~/.zsh/zsh-autosuggestions/zsh-autosuggestions.zshsource ~/.zsh/zsh-syntax-highlighting/zsh-syntax-highlighting.zshThe lines that earn their keep, in order of subtlety:
- The
compinitdance. By default zsh audits your completion directories on every shell start. The glob qualifier here runs the full rebuild only when the cache is more than a day old, and usescompinit -C— audit skipped — the other 99% of the time. Note thatEXTENDED_GLOBis load-bearing: without it,(#qN.mh+24)is not a glob qualifier but an ordinary non-empty string, the fast path never runs, and nothing anywhere complains. Worth tens of milliseconds a shell on a tidy machine, and rather more once yourfpathhas accumulated a few years of Homebrew completions. matcher-list. Case-insensitive completion:cd Doc<TAB>findsDocuments. The way it should have worked all along.SHARE_HISTORY. One history across every window, tab and tmux pane, appended as you go. Pairs beautifully withCtrl+R— and even better with fzf’s fuzzy version.HIST_IGNORE_SPACE. Prefix a command with a space and it never enters history. For commands containing things they shouldn’t.bindkey -e. Zsh takes a worrying number of cues from$EDITOR; if that’s vim, you get vi key bindings at the prompt and a confusing afternoon. This pins the readline-style bindings from our shortcuts cheat sheet.

The tool init lines assume you’ve installed Starship,
zoxide and fzf — delete any you’re not using, though you’d
be missing out. If you work across several projects, direnv earns a
line here too: its hook loads each project’s environment variables as you cd in, and removes
them as you leave.
Plugins without a plugin manager
Section titled “Plugins without a plugin manager”Two plugins earn their place in a minimal setup:
- zsh-autosuggestions — the grey ghost text that completes commands from your history. The single plugin people actually miss.
- zsh-syntax-highlighting — commands turn green when valid and red when you’ve typed rubbish. A linter for your prompt.
Install them directly:
brew install zsh-autosuggestions zsh-syntax-highlightingThen adjust the two source lines in your .zshrc to point at Homebrew’s copies:
source $(brew --prefix)/share/zsh-autosuggestions/zsh-autosuggestions.zshsource $(brew --prefix)/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zshmkdir -p ~/.zshgit clone https://github.com/zsh-users/zsh-autosuggestions ~/.zsh/zsh-autosuggestionsgit clone https://github.com/zsh-users/zsh-syntax-highlighting ~/.zsh/zsh-syntax-highlightingThe source lines in the config above already point here. Updating is git -C ~/.zsh/zsh-autosuggestions pull whenever you feel like it — which is to say your plugins
never change beneath you, a feature or a liability depending on your relationship with supply
chains.
One rule: syntax-highlighting must be sourced last — it hooks the line editor and expects to be the final word. The config above already has them in the right order.
If you later outgrow manual sourcing and want pinning and lazy loading, antidote and sheldon are the grown-up plugin managers. But two plugins don’t need a manager any more than two books need a librarian.
Shell aliases
Section titled “Shell aliases”The Oh My Zsh git plugin’s alias list is its most-loved feature, and it’s just a text file. Raid it for your favourites:
alias gst='git status'alias gco='git checkout'alias gd='git diff'alias gp='git push'alias gl='git pull'Git keeps an alias system of its own as well, which lives in ~/.gitconfig, works in every
shell and completes after git . The git config guide
has a set worth stealing, alongside the settings that stop git pull demanding an opinion.
For ls and cat, skip the aliases entirely and install the modern replacements from our
CLI starter pack — eza and bat are worth the upgrade.
grep is the exception: leave it be, type rg instead, and give ripgrep its defaults through
its own config file, which needs only an
export RIPGREP_CONFIG_PATH= line here.
When to keep the framework
Section titled “When to keep the framework”If your Oh My Zsh setup starts in 100ms and sparks joy, keep it. The point of all this isn’t
that frameworks are wicked — it’s that they’re optional, and that forty understood lines beat
four hundred inherited ones. Run the time command again now, and let the number decide.
Where next
Section titled “Where next”That’s the shell itself sorted. Complete the stack: a prompt that earns its
keep, the modern CLI starter
pack for the tools you’ll be typing at it, the keyboard
shortcuts worth memorising now that bindkey -e has
made them all work, and tmux to keep your sessions
alive when the Wi-Fi won’t. And once it’s all in place, put it in
git before the disk dies. And when the shell stops being somewhere
you type commands and becomes somewhere you write them, strict
mode is what stops those scripts failing quietly.