Featured image of post zoxide: Give cd a Memory — Jump to Any Directory in Two Keystrokes

zoxide: Give cd a Memory — Jump to Any Directory in Two Keystrokes

zoxide is a Rust-powered smart cd that remembers directories via a frecency algorithm. Pair it with --cmd cd to replace the builtin, add fzf for interactive selection, and terminal navigation becomes a two-letter affair.

My projects are scattered across a handful of directories with long, inconsistent paths. For years I either mashed cd ~/some/long/path<TAB> or dragged folders from Finder into the terminal. Then I installed zoxide. Now two or three characters is all it takes.

The trick is that my cd is no longer a shell builtin. It’s zoxide’s replacement — same behavior as the original, plus memory.

What frecency Means

zoxide’s algorithm is called frecency (frequency + recency). Every directory you visit earns a score that climbs with use and decays over time. Type cd foo and zoxide searches its database for paths containing foo, then jumps to the highest-scoring one.

Here’s what the database looks like:

1
2
3
4
5
6
$ zoxide query --score | head -5
 230.0 /Users/demo/projects/frontend
 215.3 /Users/demo/work/api-server
 198.7 /Users/demo/blog
 142.1 /Users/demo/oss/some-tool
  98.5 /Users/demo/Downloads

Frequently visited projects float to the top; stale ones sink. Data lives in a local file — fully offline, no network calls.

Install and Init

macOS via Homebrew:

1
brew install zoxide

Linux one-liner:

1
curl -sSfL https://raw.githubusercontent.com/ajeetdsouza/zoxide/main/install.sh | sh

Then initialize in your shell config. The key decision is whether to use --cmd cd:

ModeCommandEffect
Defaultzoxide init <shell>Adds z, zi commands. Builtin cd untouched
Replace cdzoxide init --cmd cd <shell>Replaces cd outright

I went with the latter. I use fish shell, so my config reads:

1
2
# ~/.config/fish/config.fish
zoxide init --cmd cd fish | source

For zsh / bash, use eval:

1
2
# ~/.zshrc
eval "$(zoxide init --cmd cd zsh)"

Why replace cd wholesale? Because zoxide’s cd is a superset of the builtin: absolute paths, relative paths, cd -, cd .. all still work. Frecency lookup only kicks in when the argument isn’t a valid path. No regression risk.

Three Daily Workflows

1. Keyword jumps. Skip full paths — just type a fragment of the directory name:

1
2
3
cd blog            # → ~/work/personal-blog
cd api             # → ~/work/api-server
cd dotfiles        # → ~/config/dotfiles

2. Multi-keyword filtering. When names collide, chain keywords to narrow down:

1
2
cd work blog       # → ~/work/personal-blog
cd client api      # → ~/work/client-project/api

Match rule: every keyword must appear in the path, and the last one must be in the final segment.

3. Interactive selection via zi. When you can’t recall the keyword or have multiple candidates:

1
cdi               # since I used --cmd cd, zi becomes cdi

This opens an fzf UI listing all candidates with live fuzzy filtering. Install fzf first if you haven’t: brew install fzf.

Advanced Tricks

Space-triggered completion. In fish, typing cd mydir<SPACE> lists multiple candidates — handy when directories share names. Fish users can also install an enhanced completion pack:

1
fisher install icezyclon/zoxide.fish

Query without jumping. Preview where zoxide would take you, without actually going:

1
2
3
4
5
zoxide query blog
# → /Users/demo/work/personal-blog

zoxide query --list blog      # list all matches
zoxide query --score          # view frecency scores

Manually register a directory. For a freshly cloned project you haven’t visited yet:

1
zoxide add ~/projects/new-repo

Exclude noise. /tmp, node_modules, and friends clutter the database:

1
set -gx _ZO_EXCLUDE_DIRS "/tmp/*" "*/node_modules/*" "$HOME/.cache/*"

Echo destination before jumping. Helps catch wrong jumps:

1
set -gx _ZO_ECHO 1

Migrate from older tools. autojump, fasd, z.lua all have import paths:

1
2
zoxide import --from=autojump ~/.local/share/autojump/autojump.txt
zoxide import --from=z ~/.z

Combining with yazi and tmux

My .zshrc has a function y that syncs yazi’s final directory back to the shell on exit:

1
2
3
4
5
6
7
8
9
function y
    set tmp (mktemp -t "yazi-cwd.XXXXXX")
    yazi $argv --cwd-file="$tmp"
    set cwd (cat -- "$tmp")
    if [ -n "$cwd" ] && [ "$cwd" != "$PWD" ]
        cd -- "$cwd"  # this cd is zoxide
    end
    rm -f -- "$tmp"
end

The trailing cd is zoxide’s version, so directories I browse through yazi also feed into the frecency database. The two tools cross-pollinate — both get smarter with use.

In tmux, each pane is its own shell, but zoxide’s database is shared globally. Visit a directory in pane A and pane B can jump there with cd foo.

When Not to Use –cmd cd

Honestly, --cmd cd isn’t uncontroversial. Arguments against overriding the builtin:

  • Shell scripts might accidentally inherit zoxide behavior
  • Shared terminals could confuse other users
  • Certain cd edge cases (like CDPATH) may behave differently

zoxide’s implementation only overrides cd in interactive shells, so the first concern is mostly academic. But if you value purity, sticking with the default z / zi gets you 95% of the benefit — you just have to pause each time to pick cd vs. z.

Personally, I prefer --cmd cd. Muscle memory doesn’t want to change, so the tool should adapt to the human, not the other way around.

References