A timesaver: replace long cd paths with z and a word or two, with every command run for real on a set of sample folders.
▶Prefer to watch? This short video explains what zoxide is for, then runs it on a real Ubuntu 24.04 machine, with on-screen captions and no voiceover. Click to play, or watch it on YouTube.
zoxide: a smarter cd. It remembers the folders you visit and takes you back to them from a few letters.
z docs jump to the best match for "docs", from anywhere
z infra ter several words narrow it down
zi pick from a list (needs fzf)
zoxide query -ls see what it has learned, with scores
zoxide is a small program that works with your shell. Once a line is in ~/.bashrc, every folder you enter is noted in a database, and a new command, z, jumps to the best match for the words you give it. Folders you use often and recently come first. So ~/projects/infra/terraform becomes z infra ter, from any other folder. There is nothing to bookmark and no alias to maintain: it learns while you work.
This page is the written version of the Timesavers video. Every command below was run for real, in an Ubuntu 24.04 container with zoxide 0.10.0 and a set of made-up project folders. The screens shown are the real screens from that run. The section "What was not run" lists what was not tried.
What zoxide is for
Use it when you move between the same few folders all day and the paths are long:
jump to a project folder z webapp, from anywhere
get into a deep folder z infra ter instead of cd ~/projects/infra/terraform
go back to the last folder z -
choose from what you know zi opens a list you can type into
use it in a script zoxide query prints the best match and nothing else
It does not replace cd: cd still works as before, and zoxide learns from it. It only knows folders you have visited since it was installed, so on the first day it knows nothing. It is a small Rust program (the Linux binary in the lab was about 1.2 MB) under the MIT licence (from the project's README).
Install and set up
Ubuntu 24.04 has a package, and the project publishes release files:
$ apt-cache policy zoxide | head -3
zoxide:
Installed: (none)
Candidate: 0.9.3-1
The Ubuntu package is version 0.9.3 (the package was not installed, only looked up). The lab used the 0.10.0 release tarball for x86_64 Linux, unpacked into ~/.local/bin. The release page has no checksum file, so none was checked. If that matters to you, take the package from Ubuntu, which is signed by the archive. Then one line in ~/.bashrc turns it on (the second line below is the one zoxide needs; the first only puts ~/.local/bin on the PATH):
$ tail -2 ~/.bashrc
export PATH="$HOME/.local/bin:$PATH"
eval "$(zoxide init bash)"
$ zoxide --version
zoxide 0.10.0
Open a new shell, or run the line once, and z and zi exist. The line makes the shell run a small script that zoxide prints, about 180 lines of bash (zoxide init bash | wc -l printed 181). The script defines z and zi and adds a hook that records the current folder. zoxide init also supports elvish, fish, nushell, posix, powershell, tcsh, xonsh and zsh (from its own help). Only bash was run.
It learns while you work
A new database is empty, so the first z finds nothing:
$ z webapp
zoxide: no match found
Now use cd as usual. zoxide notes every folder you enter (the shell hook runs when the prompt is drawn in a new folder). After a few moves, zoxide query -ls lists what it knows, best first, with a score:
$ cd ~/projects/webapp ; cd ../api-server ; cd ../infra/terraform ; cd ~/work/docs ; cd ~/projects/docs ; cd ~/work/docs ; cd ~
$ zoxide query -ls
8.0 /home/ubuntu/work/docs
4.0 /home/ubuntu/projects/docs
4.0 /home/ubuntu/projects/infra/terraform
4.0 /home/ubuntu/projects/api-server
4.0 /home/ubuntu/projects/webapp
(The seven cd commands were typed one per line in the lab.) Each visit added 4.0 to the score in this version, so work/docs, visited twice, has 8.0 and the others have 4.0. The score is zoxide's own measure and the exact numbers are not important. The database is a file, ~/.local/share/zoxide/db.zo.
Jump with z
z takes one or more words and goes to the best matching folder. The words are matched against the whole path, in order, and the last word has to match the end of the folder name (more on that below). In the lab:
ubuntu@timesaver:~$ z web
ubuntu@timesaver:~/projects/webapp$ z api
ubuntu@timesaver:~/projects/api-server$ z infra ter
ubuntu@timesaver:~/projects/infra/terraform$ z docs
ubuntu@timesaver:~/work/docs$ z -
ubuntu@timesaver:~/projects/infra/terraform$ z
ubuntu@timesaver:~$
z web went to projects/webapp, z api to api-server, and z infra ter to infra/terraform, because the two words were found in that order. z docs chose work/docs (the folder with the highest score at that moment). z - went back to where the shell was before, and z with nothing after it went to the home folder. In every case the prompt shows where you ended up.
z never goes to the folder you are standing in. It asks zoxide for the best match outside the current one. When no folder matches, it prints zoxide: no match found and stays where it is.
Frequent folders win
When several folders match, the one with the higher score is chosen. Two folders are called docs here. After the visits above, work/docs was ahead:
ubuntu@timesaver:~$ zoxide query -ls docs
12.0 /home/ubuntu/work/docs
4.0 /home/ubuntu/projects/docs
ubuntu@timesaver:~$
Then projects/docs was visited three more times (cd ~/projects/docs, then cd ~, three times). Now it is ahead, and z docs goes there (the last prompt shows where the shell ended up):
ubuntu@timesaver:~$ zoxide query -ls docs
16.0 /home/ubuntu/projects/docs
12.0 /home/ubuntu/work/docs
ubuntu@timesaver:~$ z docs
ubuntu@timesaver:~/projects/docs$
That is the whole idea: you do not rank anything yourself, your habits do. zoxide also lets old folders fade (the _ZO_MAXAGE setting limits the total score, and the oldest entries are dropped; the details of the ageing were not run, since it takes time).
One rule can surprise you. If the word you type is the name of a folder in the current directory, z goes there and does not ask the database. From ~/projects, z docs went to ./docs even though work/docs has the higher score; from your home folder, z notes went to ~/notes:
ubuntu@timesaver:~/projects$ zoxide query -l docs
/home/ubuntu/work/docs
/home/ubuntu/projects/docs
ubuntu@timesaver:~/projects$ z docs
ubuntu@timesaver:~/projects/docs$ cd ~/projects; z ./docs
ubuntu@timesaver:~/projects/docs$ cd ~; z notes
ubuntu@timesaver:~/notes$
(The score order is work/docs first, so a plain z docs would have gone there.) This is what the generated shell code does: with one argument that is a folder here, it runs cd. Use z ./docs or zoxide query when you want to be explicit.
Look before you jump: zoxide query
zoxide query prints the best match and does not move. It is what z asks internally, and it is what to use in scripts and in $( ):
ubuntu@timesaver:~$ zoxide query docs
/home/ubuntu/projects/docs
ubuntu@timesaver:~$ zoxide query -l docs
/home/ubuntu/projects/docs
/home/ubuntu/work/docs
ubuntu@timesaver:~$ zoxide query -s docs
20.0 /home/ubuntu/projects/docs
ubuntu@timesaver:~$
Without options it prints the best match. -l lists every match, best first. -s prints the score next to the result. The options in the lab's help:
Usage:
zoxide query [OPTIONS] [KEYWORDS]...
Arguments:
[KEYWORDS]...
Options:
-a, --all Show unavailable directories
-i, --interactive Use interactive selection
-l, --list List all matching directories
-s, --score Print score with results
--exclude <path> Exclude the current directory
--base-dir <path> Only search within this directory
-h, --help Print help
-V, --version Print version
--exclude leaves one folder out of the results, and --base-dir only looks inside one folder. In the lab, zoxide query --base-dir ~/work -l docs printed only /home/ubuntu/work/docs, and zoxide query -l docs --exclude ~/work/docs printed only /home/ubuntu/projects/docs. -a (--all) also shows folders that no longer exist. Use the result in a command:
$ cd $(zoxide query infra ter) && pwd
/home/ubuntu/projects/infra/terraform
Word order and misses
Order matters. The words must appear in the path in the order you type them, and the last word must match the end of a folder name:
ubuntu@timesaver:~$ zoxide query terraform infra; echo "exit status: $?"
zoxide: no match found
exit status: 1
ubuntu@timesaver:~$ zoxide query infra terraform; echo "exit status: $?"
/home/ubuntu/projects/infra/terraform
exit status: 0
ubuntu@timesaver:~$ zoxide query nothere; echo "exit status: $?"
zoxide: no match found
exit status: 1
ubuntu@timesaver:~$
terraform infra found nothing, because infra does not come after terraform in the path. infra terraform found projects/infra/terraform. A miss prints zoxide: no match found and the exit status is 1, so a script can test for it.
The last word is matched against the last part of the path, and any part of that last name will do. zoxide query proj found nothing, although projects is in the path of most entries, because projects is not the last part of any of them. zoxide query ebapp found projects/webapp, because ebapp is part of the last name, and a word with a slash in it (projects/webapp) matched too:
ubuntu@timesaver:~$ zoxide query proj; echo "exit status: $?"
zoxide: no match found
exit status: 1
ubuntu@timesaver:~$ zoxide query ebapp
/home/ubuntu/projects/webapp
ubuntu@timesaver:~$ zoxide query projects/webapp
/home/ubuntu/projects/webapp
ubuntu@timesaver:~$
If a folder was deleted, zoxide stops offering it. In the lab, a folder called gone was visited and listed. After rmdir, zoxide query -l gone printed nothing (the exit status was still 0, since -l just lists), while zoxide query --all -l gone still showed it, because the entry stays in the database until it is removed or aged out:
ubuntu@timesaver:~$ mkdir ~/gone
ubuntu@timesaver:~$ cd ~/gone
ubuntu@timesaver:~/gone$ cd ~
ubuntu@timesaver:~$ zoxide query -l gone
/home/ubuntu/gone
ubuntu@timesaver:~$ rmdir ~/gone
ubuntu@timesaver:~$ zoxide query -l gone; echo "exit status: $?"
exit status: 0
ubuntu@timesaver:~$ zoxide query --all -l gone
/home/ubuntu/gone
ubuntu@timesaver:~$
zi: choose from a list
zi (z, interactive) opens fzf with the folders zoxide knows, best first. It needs fzf; the lab used the Ubuntu package (fzf 0.44.1). Type to narrow the list, move with the arrow keys, and press Enter to go. This is the list when it opens:
$ zi
┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
│ > < 7/7 ───────────────────────────────────────────────────────────────────────────────────── │
│ > 20.0 /home/ubuntu/projects/docs │
│ 20.0 /home/ubuntu/projects/infra/terraform │
│ 12.0 /home/ubuntu/work/docs │
│ 12.0 /home/ubuntu/projects/webapp │
│ 8.0 /home/ubuntu/projects/api-server │
│ 4.0 /var/log │
│ 4.0 /home/ubuntu/Downloads │
│ ┌──────────────────────────────────────────────────────────────────────────────────────────────┐ │
│ │ README.md │ │
│ └──────────────────────────────────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘
Typing docs narrowed it to the two docs folders, with a preview of the highlighted folder in the box at the bottom:
┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
│ > docs < 2/7 ───────────────────────────────────────────────────────────────────────────────── │
│ > 20.0 /home/ubuntu/projects/docs │
│ 12.0 /home/ubuntu/work/docs │
│ │
│ │
│ │
│ │
│ │
│ ┌──────────────────────────────────────────────────────────────────────────────────────────────┐ │
│ │ README.md │ │
│ └──────────────────────────────────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘
One arrow down moved the highlight to work/docs, and Enter took the shell there. The scores in the list are the same as in zoxide query -ls. zi accepts words too (zi docs starts with the list already narrowed) and the fzf options can be set with the _ZO_FZF_OPTS variable. Both were not run; only typing in the list was.
Add and remove folders
You do not have to visit a folder to add it, and you can drop one you do not want. zoxide add puts a folder in the database, zoxide remove takes it out:
ubuntu@timesaver:~$ zoxide add ~/notes/2026
ubuntu@timesaver:~$ zoxide query -ls 2026
4.0 /home/ubuntu/notes/2026
ubuntu@timesaver:~$ z 2026
ubuntu@timesaver:~/notes/2026$ zoxide remove ~/notes/2026
ubuntu@timesaver:~/notes/2026$ cd ~
ubuntu@timesaver:~$ z 2026
zoxide: no match found
ubuntu@timesaver:~$
After the remove, z 2026 found nothing. Removing something that is not in the database prints zoxide: path not found in database and exits with status 1. To keep a folder out of the database altogether, set _ZO_EXCLUDE_DIRS to a list of globs. In the lab, with it set to /home/ubuntu/ex/a, adding ex/a and ex/b left only ex/b in the list:
$ _ZO_EXCLUDE_DIRS="$HOME/ex/a" zoxide add ~/ex/a ~/ex/b
$ zoxide query -l
/home/ubuntu/ex/b
zoxide edit opens the database for editing, and zoxide import brings in folders from another tool (atuin, autojump, fasd, z, z.lua, zsh-z; with --merge to add them to what you have). Neither was run, only their help text.
Settings you can change
The options of zoxide init are chosen in the line in ~/.bashrc. The lab's help:
$ zoxide init --help
Usage:
zoxide init [OPTIONS] <SHELL>
Arguments:
<SHELL> [possible values: bash, elvish, fish, nushell, posix, powershell, tcsh, xonsh, zsh]
Options:
--no-cmd Prevents zoxide from defining the `z` and `zi` commands
--cmd <CMD> Changes the prefix of the `z` and `zi` commands [default: z]
--hook <HOOK> Changes how often zoxide increments a directory's score [default: pwd] [possible values: none, prompt, pwd]
-h, --help Print help
-V, --version Print version
--cmd changes the name of the commands. In the lab, zoxide init bash --cmd j defined j and ji instead of z and zi. With --cmd cd it replaces cd itself (the script then defines a function called cd), so every cd is learned and cd also does the fuzzy jump. --no-cmd defines no z at all, which is for people who call zoxide query themselves. --hook changes how often a visit is counted (the help lists pwd, the default, then prompt and none). With prompt the generated script put a hook on every prompt, which was seen in the script and not tried. Only --cmd j and --no-cmd were checked, by looking at the generated script. How the hook modes behave over time was not tried.
The environment variables, as the help lists them:
Environment variables:
_ZO_DATA_DIR Path for zoxide data files
_ZO_ECHO Print the matched directory before navigating to it when set to 1
_ZO_EXCLUDE_DIRS List of directory globs to be excluded
_ZO_FZF_OPTS Custom flags to pass to fzf
_ZO_MAXAGE Maximum total age after which entries start getting deleted
_ZO_RESOLVE_SYMLINKS Resolve symlinks when storing paths
_ZO_ECHO=1 makes z print the folder it went to. It has to be set when the init line runs, because the shell code is generated then. In the lab, a shell that ran eval "$(_ZO_ECHO=1 zoxide init bash)" printed /home/ubuntu/projects/webapp after z web, and a plain init printed nothing. Putting _ZO_ECHO=1 in front of a single z command, after the init, has no effect.
What was not run in the lab
Only bash was used. Other shells (zsh, fish and the rest), Windows and macOS were not run. The zoxide edit and import commands were not run. The _ZO_MAXAGE ageing, _ZO_RESOLVE_SYMLINKS, _ZO_FZF_OPTS and the --hook modes were not tried, and neither were symbolic links in a path, a database with thousands of folders, or folders with spaces in the name. zi was run with the Ubuntu fzf only for the list shown above. The Ubuntu package (0.9.3) was looked up, not installed, so differences between it and 0.10.0 are not known. The release tarball was not checked against a checksum, because the project publishes none. Everything ran in an Ubuntu 24.04 container.
Common problems and tips
z says "no match found". The folder was not visited since zoxide was installed, or the words are in the wrong order, or the last word does not match the end of a folder name. Use zoxide query -l with the same words to see what it knows, or just cd to the folder once.
z goes to the wrong folder. Add one more word that tells the folders apart (z work docs), or use zi and pick. From a folder that contains a subfolder with the same name, z goes to that subfolder, as shown above.
z does nothing at all in a new shell. The init line is missing from ~/.bashrc, or ~/.local/bin is not on the PATH yet. Check with type z; it should say it is a function.
I installed zoxide with a package and nothing happens. The package puts the program on the PATH, but the shell line from the setup section is still needed.
A folder I deleted still shows up in zoxide query --all. It stays in the database until zoxide remove or until it ages out. z itself does not offer it.
Tips. Give two words, the last one being the end of the folder name. Use zoxide query inside $( ) and in scripts. Use zi when you are not sure, and use z - to go back. Let it learn for a day before you judge it.