Jesse A. Green / notebook

fzf User Guide

Generated April 2026 from junegunn/fzf at 06e7057

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

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 (v1 linear, v2 Smith-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 separate fzf-tmux wrapper 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 fzf on macOS, sudo apt install fzf on Debian/Ubuntu, sudo dnf install fzf on Fedora, sudo pacman -S fzf on Arch. On Windows, choco install fzf or scoop install fzf. The shell-integration scripts ship embedded in the binary and are emitted by fzf --bash, fzf --zsh, and fzf --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, or fzf --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 --man opens the full manpage from the embedded copy — useful on Windows or any system where man fzf is 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

--tmux and --popup are 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_OPTS values 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-R honours your HISTTIMEFORMAT (Bash) or zsh history settings — duplicates are de-duplicated, and selecting an entry restores the cursor to the end of the line. Press CTRL-R again inside fzf to toggle sort-by-relevance vs sort-by-recency. Press ALT-R to 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 as fzf --bash output, 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, or highlight for 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 --preview or become.

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, 'word becomes exact; in exact mode (-e / --exact), 'word becomes fuzzy. The double-quoted form 'word' is exact-boundary — match word only 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-* over transform-* — 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, prefer execute-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:

  1. Check fzf --version against https://github.com/junegunn/fzf/releases.
  2. Run fzf --help to list all currently supported options.
  3. 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:

  1. Confirm: type _fzf_complete should print a function definition.
  2. If no, source the integration: eval "$(fzf --bash)".
  3. 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:

  1. Run the preview command manually with a known input to verify it works.
  2. Check the placeholder. {} always works; {1} requires a delimiter.
  3. 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-unsafe on a multi-user machine or any non-trusted network. The unsafe variant skips API-key authentication and exposes every fzf action — including become/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.