fzf is a general-purpose command-line fuzzy finder — an interactive
filter program for any kind of list. It implements a fuzzy matching algorithm
so that you can quickly type a pattern with omitted characters and still get
the result you want. It works as a shell binding for files, history, and
directories; as a finder inside Vim, tmux, and Zellij; and as a building
block for custom interactive scripts.
1. Quick Start
fzf reads lines from standard input, lets you filter them interactively
with a fuzzy match, and prints the selected line(s) to standard output. That
single contract is all you need to know to use it for almost anything.
Key features
- Single self-contained Go binary, zero runtime dependencies, ships everywhere Go ships (Linux, macOS, Windows, FreeBSD, OpenBSD, Android, ARM/x86/RISC-V).
- Fuzzy matching with two algorithms (
v1linear,v2Smith-Waterman) plus exact, prefix, suffix, equal, and exact-boundary matchers — all selectable via search syntax. - Rich shell integration for Bash, Zsh, and Fish — file widget (CTRL-T),
history widget (CTRL-R), directory widget (ALT-C), and
**completion trigger. - Configurable preview window with full ANSI color, scrolling, wrapping, and dynamic content via shell commands.
- 170+ built-in actions bindable to any key, mouse, or runtime event
(
focus,change,load,start,result,resize,jump, …). - Live transformation primitives (
transform,bg-transform,reload,become,execute) for building interactive multi-stage workflows inside a single fzf invocation. - Optional HTTP server (
--listen) for receiving actions from external processes — drives editor plugins and remote control workflows. - Tmux popup mode (
--tmux) and Zellij popup support, plus a separatefzf-tmuxwrapper for environments that need it.
Prerequisites
fzf is one statically linked binary. It needs a terminal that supports
xterm-style escape sequences. On Unix it works with any reasonable shell;
on Windows, the modern Windows Terminal, ConEmu, mintty, and WSL all work.
Tip
On Linux and macOS, the fastest path is your package manager:
brew install fzfon macOS,sudo apt install fzfon Debian/Ubuntu,sudo dnf install fzfon Fedora,sudo pacman -S fzfon Arch. On Windows,choco install fzforscoop install fzf. The shell-integration scripts ship embedded in the binary and are emitted byfzf --bash,fzf --zsh, andfzf --fish.
30-second smoke test
# Pipe a list, filter interactively, print the chosen line:
ls | fzf
Type a few letters of any filename, press Enter, and that filename is printed to stdout. The exit status reflects the outcome:
0 selection accepted
1 no match (input was non-empty)
2 error
130 user pressed Ctrl-C / Esc / SIGINT
126 --bind become(...) replaced the process (this is the new program's exit, not fzf's)
Capture the result and use it
file=$(ls | fzf) && echo "you picked: $file"
you picked: README.md
Where to go next
- Set up shell integration so CTRL-T, CTRL-R, and ALT-C work everywhere → see Section 2 (Installation) and Section 4 (Shell integration).
- Add a preview window to see file contents while you scroll → Section 5 (Preview window).
- Build interactive multi-stage scripts with
--bind→ Section 7 (Key bindings, actions, and events) and the Admin Guide for the full action catalog.
2. Installation
2.1 Choose an install method
fzf is distributed as: a single binary you can drop anywhere, a Homebrew
formula, packages in every major Linux/BSD distro, a Chocolatey/Scoop/winget
package on Windows, and a Vim plugin. Below are the canonical methods.
Note
All methods install the same binary. The shell integration scripts are embedded into the binary — they print to stdout when you run
fzf --bash,fzf --zsh, orfzf --fish. There is no separate "shell integration package" to install for any of them.
macOS (Homebrew)
brew install fzf
After installing, set up the key bindings (CTRL-T, CTRL-R, ALT-C) and shell completion by sourcing the embedded scripts:
# Bash
eval "$(fzf --bash)"
# Zsh
source <(fzf --zsh)
# Fish
fzf --fish | source
Add the relevant line to your ~/.bashrc, ~/.zshrc, or
~/.config/fish/config.fish to load it automatically.
Debian / Ubuntu
sudo apt install fzf
Then enable bindings as above. Older Debian releases ship outdated fzf;
for the current release use the Homebrew-on-Linux method or the install
script (next section).
Fedora / RHEL
sudo dnf install fzf
Arch Linux
sudo pacman -S fzf
Windows (Chocolatey / Scoop / winget)
Chocolatey
choco install fzf
Scoop
scoop install fzf
winget
winget install fzf
Cross-platform install script (Git clone)
git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
~/.fzf/install
The installer asks whether to enable fuzzy auto-completion, key bindings,
and to update your shell rc files. It writes the embedded shell scripts to
~/.fzf/shell/ and adds a single sourcing line to your rc.
Important
The Git-clone install method downloads a prebuilt binary matching your platform from the GitHub releases page. If your environment lacks internet access during install, you must build from source — see the Admin Guide, Building from Source.
Manual binary install
Download the matching archive from
https://github.com/junegunn/fzf/releases, extract the fzf (or
fzf.exe) binary, and put it somewhere on your $PATH. Then enable shell
integration with the eval / source lines above.
2.2 Verify the install
fzf --version
0.71 (a1b2c3d)
The first token is the semver-style release; the parenthesised token is
the build commit (or devel for a development build).
echo "hello world" | fzf
Press Enter on the only line. fzf prints hello world and exits 0. If
that works, the binary is healthy.
Tip
fzf --manopens the full manpage from the embedded copy — useful on Windows or any system whereman fzfis unavailable.
2.3 Tmux popup mode (optional)
If you use tmux 3.3 or later, fzf can open in a floating popup:
echo -e "one\ntwo\nthree" | fzf --tmux
The fzf-tmux wrapper (a separate shell script that ships alongside the
binary in most distributions) provides finer-grained popup control on
older tmux versions. On modern tmux you should prefer --tmux.
Note
--tmuxand--popupare aliases for the same feature. Format:--tmux=center,80%,40%opens an 80%-wide, 40%-tall popup centered. Position keywords:center,top,bottom,left,right.
3. Configuration
fzf does not have a config file in the traditional sense. Instead, it
reads options from environment variables and from an optional defaults
file. This keeps the configuration model identical across shells.
3.1 The four cardinal environment variables
| Variable | Purpose |
|---|---|
FZF_DEFAULT_COMMAND |
Command to use when fzf's stdin is a TTY (no input piped). Typical value: a fast file finder like fd --type f or rg --files. |
FZF_DEFAULT_OPTS |
Default command-line options applied to every invocation (e.g. --height 40% --layout=reverse --border). |
FZF_DEFAULT_OPTS_FILE |
Path to a file containing default options (one per line, or \-continued). Read before FZF_DEFAULT_OPTS. |
FZF_API_KEY |
API key required by the HTTP server (--listen) when it binds to a non-local address. |
3.2 A starter FZF_DEFAULT_OPTS
export FZF_DEFAULT_OPTS='
--height 40%
--layout=reverse
--border=rounded
--preview="cat {}"
--preview-window=right:60%:wrap
--bind=ctrl-y:accept
--color=fg:#d0d0d0,bg:#121212,hl:#5f87af
'
Tip
Multi-line
FZF_DEFAULT_OPTSvalues work because fzf splits options with shell-style word splitting. You can keep your config readable without backslash gymnastics.
3.3 Shell-integration environment variables
These tune the shell widgets (CTRL-T, CTRL-R, ALT-C) and the **
fuzzy completion trigger. They are read by the shell-integration scripts
emitted by fzf --bash, fzf --zsh, and fzf --fish.
| Variable | Default | Effect |
|---|---|---|
FZF_CTRL_T_COMMAND |
platform default walker | Command used by CTRL-T to enumerate files. |
FZF_CTRL_T_OPTS |
empty | Extra fzf options for CTRL-T. |
FZF_CTRL_R_OPTS |
empty | Extra fzf options for CTRL-R history. |
FZF_ALT_C_COMMAND |
platform default walker | Command used by ALT-C to enumerate directories. |
FZF_ALT_C_OPTS |
empty | Extra fzf options for ALT-C. |
FZF_COMPLETION_TRIGGER |
** |
The trigger sequence for fuzzy completion. |
FZF_COMPLETION_OPTS |
empty | Extra fzf options for ** completion. |
FZF_COMPLETION_PATH_OPTS |
empty | Extra path-completion fzf options. |
FZF_COMPLETION_DIR_OPTS |
empty | Extra directory-completion fzf options. |
FZF_TMUX |
0 |
Set non-zero to route the bindings through fzf-tmux. |
FZF_TMUX_HEIGHT |
40% |
Tmux popup height for shell widgets. |
FZF_TMUX_OPTS |
empty | Extra fzf-tmux options. |
3.4 Disabling a single binding
To disable any of CTRL-T, CTRL-R, or ALT-C, set the corresponding
*_COMMAND variable to an empty string before sourcing the integration
script. See Section 4.
Note
Full reference of every config option, every CLI flag, and the placeholder grammar lives in the Admin Guide, Section 3 (Configuration Reference).
4. Shell integration
Sourcing fzf --bash (or --zsh / --fish) installs three default key
bindings and one fuzzy completion trigger.
4.1 The three default key bindings
| Binding | Action |
|---|---|
CTRL-T |
Pick file(s) from the current directory tree and paste the path(s) onto the command line. |
CTRL-R |
Pick a command from shell history and paste it onto the command line. The exact command shows up in your prompt; press Enter to run, edit first, or Ctrl-C to discard. |
ALT-C |
Pick a directory from the current tree and cd into it. |
Tip
CTRL-Rhonours yourHISTTIMEFORMAT(Bash) or zsh history settings — duplicates are de-duplicated, and selecting an entry restores the cursor to the end of the line. PressCTRL-Ragain inside fzf to toggle sort-by-relevance vs sort-by-recency. PressALT-Rto toggle showing raw multi-line history entries.
4.2 The ** completion trigger
After the integration script is sourced, type **<TAB> after most
commands to fuzzy-complete files, directories, processes, hostnames,
environment variables, or aliases.
vim **<TAB> # fuzzy-pick a file
cd **<TAB> # fuzzy-pick a directory
kill -9 **<TAB> # fuzzy-pick a running process
ssh **<TAB> # fuzzy-pick a known hostname
unset **<TAB> # fuzzy-pick an environment variable
unalias **<TAB> # fuzzy-pick a defined alias
The trigger sequence is ** by default; change it with
FZF_COMPLETION_TRIGGER='~~' (or whatever you like).
4.3 Per-command custom completion
The integration script ships built-in pickers for cd, pushd, rmdir,
vi, vim, nvim, code, git, grep, rg, ls, cat, less,
ssh, kill, and many others. To add your own:
# Bash
_fzf_setup_completion path tldr # path completion for tldr
_fzf_setup_completion dir cd cdr # directory completion for two cmds
_fzf_setup_completion var export unset # variable completion
_fzf_setup_completion proc pkill # process completion
_fzf_setup_completion alias unalias # alias completion
_fzf_setup_completion host ssh sftp # hostname completion
Note
Custom commands also need an exec definition. To override how files are enumerated for the path completion of one command, define a function named
_fzf_complete_<cmd>()that prints candidate lines on stdout and ends with_fzf_complete "$@" -- "$@".
4.4 Disabling parts of the integration
# Disable just the CTRL-R history widget; keep CTRL-T and ALT-C
export FZF_CTRL_R_COMMAND=
eval "$(fzf --bash)"
Warning
Some Linux package builds split the integration into separate files under
/usr/share/fzf/. Their content is the same asfzf --bashoutput, but they may lag the binary by a release. When in doubt,eval "$(fzf --bash)"is always current.
5. Preview window
The preview window shows command output beside the candidate list. It is the single feature that turns fzf from a fuzzy filter into a complete file/code/log browser.
5.1 Basic preview
ls | fzf --preview 'cat {}'
{} is the current line placeholder. fzf substitutes the current
selection into the command and re-runs it whenever the cursor moves.
Tip
Use
bat,glow, orhighlightfor syntax-coloured previews:--preview 'bat --color=always --style=numbers {}'.
5.2 Preview window layout
fzf --preview 'cat {}' --preview-window 'right:60%:wrap'
--preview-window accepts a comma-separated spec:
| Token | Meaning |
|---|---|
up / down / left / right |
Position relative to the list. |
<N>% or <N> |
Size as a percentage or absolute lines/columns. |
wrap / nowrap |
Wrap long lines (wrap-word for word boundaries). |
cycle / nocycle |
Cycle around when scrolling past edges. |
follow / nofollow |
Auto-scroll to the bottom when the preview command produces streaming output (e.g. tail -f). |
info / noinfo |
Show or hide the position indicator. |
hidden / nohidden |
Initially hidden; show with the toggle-preview action. |
border-rounded / border-sharp / border-bold / border-block / border-thinblock / border-double / border-horizontal / border-vertical / border-line / border-none |
Border style. Aliases: rounded, sharp, etc. |
+SCROLL[OFFSETS] |
Initial scroll position. +{2}-/2 = jump to line 2 of {2}, then back-scroll half a screen. |
~HEADER_LINES |
Pin the first N lines of the preview as a header. |
<SIZE_THRESHOLD(ALT_LAYOUT) |
Use ALT_LAYOUT instead when the terminal is smaller than SIZE_THRESHOLD columns. |
Example with two layouts:
fzf --preview 'less -R {}' \
--preview-window 'right,60%,border-rounded,<80(down,30%,border-horizontal)'
That spec says: by default, preview on the right with 60% width and a rounded border; if the terminal is narrower than 80 columns, drop into a bottom layout with 30% height and only top/bottom border lines.
5.3 Preview placeholders
| Placeholder | Substitutes |
|---|---|
{} |
The current line (no parsing). |
{1}, {2}, … |
Field N from the line, split by --delimiter (default: AWK). |
{1..3}, {2..}, {..-2} |
Field ranges. |
{q} |
The current query. |
{q:2}, {q:1..3} |
Field N (or field range) of the query, after delimiter splitting. |
{n} |
The zero-based item index. |
{+} |
All currently selected items (multi-select), space-separated. |
{*} |
All matching items in the current result. |
{f} |
Path to a temp file containing the current item (avoids quoting issues). |
{+f} |
Path to a temp file containing all selected items. |
{r} |
Raw line; do not shell-quote (advanced). |
Important
Placeholders are quoted using POSIX shell rules by default. If you need raw output (because your value already contains valid shell), use
{r}or pipe to a sub-shell — but be aware of the injection surface. Never wrap an unsanitised user-controlled string in--previeworbecome.
5.4 Toggling, scrolling, refreshing preview
| Action | Description |
|---|---|
toggle-preview |
Show/hide the preview window. Bind to e.g. --bind 'ctrl-/:toggle-preview'. |
change-preview-window(<spec>) |
Cycle through preview specs (separate alternates with |). |
preview-up / preview-down |
Scroll preview by one line. |
preview-page-up / preview-page-down |
Scroll by a full page. |
preview-half-page-up / preview-half-page-down |
Scroll by half a page. |
preview-top / preview-bottom |
Jump to start / end. |
refresh-preview |
Force re-running the preview command. |
change-preview(<command>) |
Replace the preview command at runtime. |
transform-preview-label(<command>) |
Recompute the preview label. |
A common preview-toggle binding:
fzf --preview 'cat {}' \
--bind 'ctrl-/:toggle-preview,ctrl-u:preview-half-page-up,ctrl-d:preview-half-page-down'
6. Search syntax (extended-search mode)
By default, fzf is in extended-search mode: every space in your query is an AND. Each token can carry a prefix or suffix that switches the matcher type.
| Token | Match type | Example |
|---|---|---|
sbtrkt |
Fuzzy match | matches "Smith Brackets Trk T" |
'wild |
Exact-match (substring) | matches anything containing "wild" verbatim |
'wild' |
Exact-match (word boundary) | matches "wild" surrounded by non-word chars |
^music |
Prefix-exact match | matches lines starting with "music" |
.mp3$ |
Suffix-exact match | matches lines ending with ".mp3" |
!fire |
Inverse-exact match | matches anything NOT containing "fire" |
!^music |
Inverse-prefix match | matches lines that do NOT start with "music" |
!.mp3$ |
Inverse-suffix match | matches lines that do NOT end with ".mp3" |
Tokens separated by spaces are AND-ed. Tokens separated by | are OR-ed
(but only between consecutive tokens of the same group):
^core go$ | rb$ | py$
Reads as: lines starting with core AND ending with one of go, rb,
or py.
Note
'flips fuzziness. In default fuzzy mode,'wordbecomes exact; in exact mode (-e/--exact),'wordbecomes fuzzy. The double-quoted form'word'is exact-boundary — matchwordonly between non-word characters.
6.1 Case sensitivity
| Flag | Behaviour |
|---|---|
(default) --smart-case |
Case-insensitive if your query is all lowercase, case-sensitive otherwise. |
-i, --ignore-case |
Always case-insensitive. |
+i, --no-ignore-case |
Always case-sensitive. |
6.2 Choosing the algorithm
| Flag | Effect |
|---|---|
| (default) v2 | Modified Smith-Waterman. Best match quality. Slightly slower per match but parallelised across cores. |
| v1 fallback | Linear-time fuzzy. Faster on huge inputs; less optimal scoring. fzf falls back to v1 automatically when input exceeds heuristic limits. |
--exact / -e |
Disable fuzziness — every token must appear verbatim. |
--no-extended / +x |
Disable the extended-search mode entirely; treat the query as a single fuzzy term. |
6.3 Sort and tiebreak
| Flag | Effect |
|---|---|
--no-sort / +s |
Preserve input order even after matching. |
--scheme=default |
Default scoring (good for general lists). |
--scheme=path |
Path-aware scoring: bonuses on path separators and at the start of basenames. |
--scheme=history |
History-aware scoring: bonuses for command-line patterns. |
--tiebreak=length,chunk,pathname,begin,end,index |
Up to 3 tiebreaks, comma-separated. index (insertion order) must be last when used. |
7. Key bindings, actions, and events
--bind is fzf's most powerful option. It maps keys or events
(window resize, query change, item focus, …) to one or more actions.
7.1 Bind syntax
--bind '<trigger>:<action>[+<action>...][,<trigger>:<action>...]'
Triggers are keys (ctrl-x, alt-up, f3, enter, tab) or events
(change, start, load, focus, result, resize, one, zero,
jump, jump-cancel, click-header, click-footer, multi,
backward-eof).
Actions are strings drawn from the action catalog (see Admin Guide,
Section 3.4). Multiple actions chain with +. Multi-line action
arguments wrap in matching parens, brackets, or braces — fzf parses to
the matching close.
7.2 The four most useful action families
Execute — run a command without leaving fzf
fzf --bind 'ctrl-y:execute(echo {} | pbcopy)+abort'
execute(...) runs the command after fzf temporarily releases the TTY,
so interactive programs work correctly. execute-silent(...) keeps the
TTY held by fzf and discards stdout/stderr — better for noisy commands.
Become — replace fzf with a new program
fzf --bind 'enter:become(vim {})'
become(...) execs the given command in fzf's place (exit code 126
indicates a become happened). Use it when you want fzf to be a launcher
that vanishes once you've picked.
Reload — replace the candidate list
fzf --bind 'ctrl-r:reload(rg --files .)'
reload(...) re-runs the source command and replaces the input. Useful
for live filtering across multiple data sources.
Transform — rewrite fzf's own state on the fly
fzf --bind 'ctrl-t:transform-prompt(echo "{}> ")'
transform-prompt, transform-query, transform-header,
transform-footer, transform-preview-label, transform-border-label,
and friends let you swap a piece of the UI for the stdout of an arbitrary
command. The bg-transform-* variants run in the background so they
don't block the UI.
Tip
When a command takes longer than a few hundred milliseconds, prefer
bg-transform-*overtransform-*— the UI stays responsive and the result is applied as soon as it's ready.
7.3 Events you can bind
| Event | Fires when … |
|---|---|
start |
fzf has finished startup. |
load |
All input has been read from stdin / the source command. |
focus |
The current item changes (cursor moves, list updates). |
change |
The query changes. |
result |
The result list updates. |
resize |
The terminal is resized. |
one |
The result count just became exactly 1. |
zero |
The result count just became 0. |
jump |
The user enters jump mode (default ctrl-j). |
jump-cancel |
Jump mode is cancelled without selection. |
click-header / click-footer |
A clickable header/footer field is clicked. |
multi |
The selection set changes. |
backward-eof |
Backspace is pressed when the query is already empty. |
7.4 Often-bound default keys (every line is bindable)
| Key | Default action |
|---|---|
enter |
accept |
ctrl-c / ctrl-g / esc |
abort |
ctrl-j / ctrl-n / down |
down |
ctrl-k / ctrl-p / up |
up |
tab |
toggle+down (multi-select) |
shift-tab |
toggle+up |
ctrl-a |
select-all (multi) |
ctrl-d |
deselect-all (multi) |
ctrl-r |
toggle-sort (within fzf — not the shell history widget) |
page-up / page-down |
page-up / page-down |
home / end |
beginning-of-line / end-of-line |
The full action catalog (170+ entries, including all change-*,
transform-*, bg-transform-*, toggle-*, preview, history, jump,
become/reload/execute, listen-server interactions) is enumerated in the
Admin Guide.
Warning
Some actions modify shell completion or terminal state in non-reversible ways inside
execute(...). If you need fzf to stay visually clean, preferexecute-silent(...)and write to a tempfile instead of inheriting the terminal.
8. Common tasks
The following recipes are battle-tested patterns drawn from the official
manpage and ADVANCED.md. Each one ends with the exact command you can
copy.
8.1 Open a file in your editor
fzf --bind 'enter:become(${EDITOR:-vim} {})'
Replaces fzf with your editor on the chosen file. Works because of
become(...)'s exec semantics.
8.2 Live ripgrep inside fzf
INITIAL_QUERY=""
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
fzf --ansi --disabled --query "$INITIAL_QUERY" \
--bind "start:reload:$RG_PREFIX {q}" \
--bind "change:reload:sleep 0.1; $RG_PREFIX {q} || true" \
--delimiter : \
--preview 'bat --color=always {1} --highlight-line {2}' \
--preview-window 'up,60%,border-bottom,+{2}+3/3,~3' \
--bind 'enter:become(${EDITOR:-vim} +{2} {1})'
--disabled turns off fzf's own matching — every keystroke instead
re-runs ripgrep (reload:). The result: search code with regex power and
fzf's UX.
8.3 Pick from a long list with preview
ls | fzf --preview 'file {}; echo; head -50 {}' \
--preview-window=down,50%,wrap
file {} shows the file type; head -50 {} shows a snippet. The
preview command is just shell — you can chain whatever you want.
8.4 Multi-select and operate on the selection
ls | fzf --multi \
--bind 'ctrl-a:select-all,ctrl-d:deselect-all' \
--bind 'enter:execute(rm -i {+})'
{+} expands to all selected items, space-separated and shell-quoted.
The interactive rm -i confirms each one before deletion.
8.5 Browse Git log with diff preview
git log --color=always --format="%C(auto)%h%d %s %C(black)%C(bold)%cr" |
fzf --ansi --no-sort \
--preview 'git show --color=always {1}' \
--preview-window=down,70%,wrap
8.6 Kill a process by fuzzy match
ps -ef | fzf --multi --header-lines=1 \
--bind 'enter:execute(kill -9 {2})+abort'
--header-lines=1 keeps the ps header always visible above the list.
8.7 Switch tmux session
tmux list-sessions -F '#S' | fzf --bind 'enter:execute(tmux switch-client -t {})+abort'
8.8 SSH to a known host
fzf < <(awk '/^Host / {print $2}' ~/.ssh/config) \
--bind 'enter:become(ssh {})'
The shell integration's ** trigger does this automatically:
ssh **<TAB>.
8.9 Browse and cd to a Git-tracked directory
git ls-files | xargs -n1 dirname | sort -u |
fzf --bind 'enter:become(cd {})'
(Chain with pushd or use ALT-C if you've enabled the integration.)
8.10 Build a typeahead picker around any command
seq 1 100 | fzf --print-query --bind 'enter:accept-or-print-query'
accept-or-print-query accepts the current item if there is one, or
prints the raw query to stdout when no candidate matches — turns fzf
into "pick an existing one OR enter a new value".
9. Troubleshooting
Error: unknown option: --foo
Cause: the option is misspelt or your installed fzf is older than the release that introduced it.
Solution:
- Check
fzf --versionagainsthttps://github.com/junegunn/fzf/releases. - Run
fzf --helpto list all currently supported options. - Upgrade via your package manager.
Prevention: pin your tooling. fzf release notes flag breaking changes.
Error: unknown action: <name>
Cause: an action used in --bind is misspelt or belongs to a newer
fzf release. Action names are kebab-case and the parser is strict.
Solution: check spelling against the Admin Guide action catalog, or
match the binding from --help output. Frequently-mistaken pairs:
accept-non-empty (not accept-nonempty), clear-query (not
reset-query), toggle-preview-wrap (not toggle-wrap-preview).
Error: bind action not specified: <key>
Cause: --bind 'ctrl-x' with no :action part. fzf requires an
action for every bound key.
Solution: add an action: --bind 'ctrl-x:abort'. To unbind a default
key, bind it to ignore: --bind 'ctrl-x:ignore'.
Error: template should include at least 1 placeholder: ...
Cause: A --header, --prompt, --ghost, --info-command, or
--preview value uses a templated form ({...}) that has no
placeholders. fzf rejects it because the output would be static.
Solution: either include {}/{1}/{q} in the template, or use the
non-template variant (--prompt 'literal> ' instead of
--transform-prompt).
Error: FZF_API_KEY is required to allow remote access
Cause: --listen was given a non-localhost bind (e.g.
--listen 0.0.0.0:6266) but FZF_API_KEY is not set.
Solution:
export FZF_API_KEY=$(openssl rand -hex 16)
fzf --listen 0.0.0.0:6266
Prevention: if you don't actually need remote access, use plain
--listen 6266 (binds to localhost) or --listen ./fzf.sock (Unix
socket). For remote use, the API key is mandatory.
Error: socket already in use: <path>
Cause: a previous fzf instance was killed without unlinking its Unix socket file.
Solution: rm <path> and retry. If multiple users share the same
path, switch to per-user paths or use TCP.
Error: invalid color specification: <spec>
Cause: --color=… value is malformed. The grammar is comma-separated
group:fg,bg,attr triples (each part optional), where group is one of
fg, bg, hl, pointer, marker, prompt, info, border,
spinner, header, gutter, selected-fg, selected-bg, selected-hl,
preview-fg, preview-bg, nth, label, separator, scrollbar, etc.
Solution: start from a base scheme (--color=dark, --color=light,
--color=base16, --color=bw) and override specific groups:
--color=dark,fg:#cccccc,hl:bright-yellow,marker:cyan:bold.
Issue: shell integration "feels broken" after upgrading fzf
Cause: old ~/.fzf.bash / ~/.fzf.zsh / ~/.fzf.fish files
generated by an older installer no longer match the binary.
Solution: regenerate by sourcing the embedded scripts directly:
# ~/.bashrc: drop any old `[ -f ~/.fzf.bash ] && source ...` line
# and replace with:
eval "$(fzf --bash)"
The embedded scripts are always in lock-step with the binary.
Issue: **<TAB> does nothing
Cause: the completion script wasn't sourced for your shell, OR
another tool is already binding **.
Solution:
- Confirm:
type _fzf_completeshould print a function definition. - If no, source the integration:
eval "$(fzf --bash)". - If yes, change the trigger:
export FZF_COMPLETION_TRIGGER=';;'.
Issue: preview window is blank
Cause: the preview command produced no output, or the placeholder expanded to nothing.
Solution:
- Run the preview command manually with a known input to verify it works.
- Check the placeholder.
{}always works;{1}requires a delimiter. - Add
bash -c 'set -x; cat {}'and watch the preview window.
Issue: very slow startup on macOS
Cause: built-in macOS awk chokes on non-UTF-8 input from the
shell-integration scripts.
Solution: the scripts already detect this and prefer mawk / gawk
when available. Install one: brew install mawk or brew install gawk.
Issue: history widget (CTRL-R) shows duplicates
Cause: Bash history is loaded from disk and the in-memory history both — and de-duplication in CTRL-R only de-dups exact matches.
Solution: add HISTCONTROL=ignoredups:erasedups and shopt -s histappend to ~/.bashrc.
Issue: ALT-C doesn't change directory
Cause: Some terminals (notably macOS Terminal.app's default profile)
don't send the Esc byte for ALT keys. fzf binds \ec and never sees
the modifier.
Solution: in Terminal.app preferences, enable "Use Option as Meta key". For iTerm2, set the Option key behaviour to "Esc+".
Caution
Never run
fzf --listen-unsafeon a multi-user machine or any non-trusted network. The unsafe variant skips API-key authentication and exposes every fzf action — includingbecome/execute/reload— to any process that can reach the listening port.
10. FAQ
Q: Can I use fzf without sourcing the shell integration?
Absolutely. fzf is a normal Unix filter — pipe in, pipe out. The shell integration is convenience for CTRL-T / CTRL-R / ALT-C; it's optional.
Q: How do I tell fzf to use fd or rg for the file walker?
Set FZF_DEFAULT_COMMAND='fd --type f --hidden --follow --exclude .git'
(or rg --files --hidden --follow --glob '!.git/'). fzf uses this when
its stdin is a TTY. For the CTRL-T widget, use
FZF_CTRL_T_COMMAND='fd ...'.
Q: Does fzf work on Windows without WSL?
Yes. Native Windows builds ship as fzf.exe. They work in modern Windows
Terminal, ConEmu, mintty, and WSL. The shell integration scripts work
under Git Bash, MSYS2, and Cygwin; PowerShell support is community-driven
via PSFzf.
Q: What's the difference between --multi and --multi=N?
--multi (or -m) enables multi-select with no upper bound (capped at
INT_MAX). --multi=3 caps the selection set at 3 items — once reached,
further tab is a no-op until something is deselected.
Q: How do I make CTRL-R show the most recent commands first?
Inside the history widget, CTRL-R toggles between sort-by-relevance
(default) and sort-by-recency. To make recency the default, add
--bind=ctrl-r:toggle-sort to FZF_CTRL_R_OPTS.
Q: Can I have a different preview command per file type?
Yes — use a wrapper script that dispatches by extension:
fzf --preview 'preview-router {}'
where preview-router shells out to bat for code, mediainfo for
videos, exiftool for images, etc.
Q: How do I disable colour entirely?
fzf --no-color for one invocation, or set NO_COLOR=1 (the
no-color.org standard) for all of them. Add
--color=bw if you also want a black-and-white reverse-video selector.
Q: Why does my custom binding not fire?
Most often: the action name is wrong, OR another binding consumes the
key first. Try --bind '?:show-input+ignore' to dump the current
input state and keys (it's a debug nudge, not a feature).
Q: Does fzf send any data anywhere?
No. fzf is fully offline — no telemetry, no update checks, no network
activity. The HTTP server (--listen) only opens when you explicitly
request it.
Q: Where do I report bugs or request features?
https://github.com/junegunn/fzf/issues. Before opening, run
fzf --version and include the output, your shell, your OS, and a
minimal reproducing command.
Q: How do I bind a key to clear the query?
fzf --bind 'ctrl-l:clear-query'
Q: What does the exit code 130 mean?
The user pressed Ctrl-C / Esc / SIGINT. This is the standard convention
for "user interrupted" inherited from POSIX kill(SIGINT) -> 128 + 2.
Q: Does fzf integrate with Vim?
Yes — the plugin/fzf.vim shipped in the repo is a thin Vim/Neovim
plugin around the binary, providing the :FZF command. The full-fat
plugin with :Files, :Buffers, :Rg, etc. is the separate
junegunn/fzf.vim repository.