Configuring fzf globally via FZF_DEFAULT_COMMAND and FZF_DEFAULT_OPTS, adjusting each keybinding with per-bind variables, and applying the best configuration patterns so your settings stay consistent and easy to manage.

In episode 7 you glimpsed variables like FZF_CTRL_T_COMMAND and FZF_CTRL_R_OPTS. Episode 8 ties it all together: fzf reads its settings from environment variables built systematically, and understanding this pattern is the key to managing fzf without mixing things up. It all starts with a simple export line like export FZF_DEFAULT_OPTS="--height=40%".
Think of every fzf invocation as an airplane. The FZF_DEFAULT_OPTS variable is the global cabin setting (every plane is the same), while the per-keybinding variables are settings specific to one route. If you guess without understanding this hierarchy, the result is configuration conflicts that are hard to trace.
All fzf configuration flows through environment variables in two families:
| Family | Meaning |
|---|---|
FZF_DEFAULT_* | Applies globally: every fzf invocation |
FZF_CTRL_T_*, FZF_CTRL_R_*, FZF_ALT_C_* | Applies to a specific keybinding only |
There are also FZF_COMPLETION_* (for fuzzy completion, episode 9) and FZF_TMUX_* (for tmux integration, its own episode). The naming pattern is always the same: FZF_<CONTEXT>_<ROLE> where COMMAND defines the data source and OPTS defines the command-line options.
When fzf's input is not piped (for example, you type fzf directly), fzf needs a data source. The default is simple directory walking, but you can replace it with any command:
export FZF_DEFAULT_COMMAND="find . -type f -not -path '*/.git/*'"It's important to understand: FZF_DEFAULT_COMMAND applies when fzf runs without stdin. For keybindings like CTRL-T, the data source comes from each keybinding's own dedicated variable (not directly from this one) — details in the section below. Use this variable for any file list you open often, for example combining several project directories:
export FZF_DEFAULT_COMMAND="find ~/code ~/docs -type f 2>/dev/null"Note
A common mistake is storing lots of options in FZF_DEFAULT_COMMAND. This variable is only for the data-producing command; options like layout, preview, and colors go into FZF_DEFAULT_OPTS — don't mix them.
This is the most important variable. Every option you've learned since episode 4 — --height, --layout, --border, --color, --preview, --bind — can be collected here so every fzf invocation is consistent:
export FZF_DEFAULT_OPTS="--height=40% --layout=reverse --border --info=inline \
--preview='bat --color=always --style=numbers --line-range=:200 {}' \
--color=prompt:#50fa7b,pointer:#ff79c6,hl:#bd93f9"Important
Note the quotes above. Because the variable's value is split by the shell like arguments, options containing spaces must be quoted inside the value. --preview='bat ... {}' inside the outer double quotes is stored as one intact argument — the inner quotes are preserved, not stripped by the shell.
There is one more that's often missed: FZF_DEFAULT_OPTS_FILE. If your settings are very long, move the contents of FZF_DEFAULT_OPTS into a file and point this variable at it:
export FZF_DEFAULT_OPTS_FILE="$HOME/.config/fzf/opts"The benefits: options are no longer bound by shell quote escaping, they can have comments, and they're easier to version-control. fzf still reads FZF_DEFAULT_OPTS and FZF_DEFAULT_OPTS_FILE at the same time — both are merged.
Each keybinding from episode 7 has a COMMAND and OPTS variable pair. They set the data source and options for a specific fzf invocation, and override or add on top of FZF_DEFAULT_OPTS:
| Variable | Keybinding | Role |
|---|---|---|
FZF_CTRL_T_COMMAND | CTRL-T | File/directory source |
FZF_CTRL_T_OPTS | CTRL-T | Specific options |
FZF_CTRL_R_OPTS | CTRL-R | Specific options (history) |
FZF_ALT_C_COMMAND | ALT-C | Directory source |
FZF_ALT_C_OPTS | ALT-C | Specific options |
Note: there is no FZF_CTRL_R_COMMAND — CTRL-R's source is always the shell history; it cannot be changed. This is consistent with how episode 7 works.
Here's a typical combination: CTRL-R uses the history scheme, while CTRL-T uses preview and multi-select:
export FZF_CTRL_R_OPTS="--scheme=history --sort"
export FZF_CTRL_T_OPTS="--preview='bat --color=always {}' --multi"Because OPTS is added to FZF_DEFAULT_OPTS, you don't need to repeat global options in every variable — just add the specific ones. If a per-keybinding OPTS contains the same option as the global one, the value from that OPTS wins.
The best fzf setup is one another person can read in a minute. The recommendation: a tidy block of sections at the end of your config, ordered from global to specific.
# ---------- Fzf: global data source ----------
export FZF_DEFAULT_COMMAND="find . -type f -not -path '*/.git/*'"
# ---------- Fzf: global options ----------
export FZF_DEFAULT_OPTS="--height=40% --layout=reverse --border \
--info=inline --prompt='> ' \
--preview='bat --color=always --style=numbers --line-range=:200 {}' \
--color=prompt:#50fa7b,pointer:#ff79c6,hl:#bd93f9"
# ---------- Fzf: per keybinding options ----------
export FZF_CTRL_T_OPTS="--preview='ls -la' --multi"
export FZF_CTRL_R_OPTS="--scheme=history --sort"
export FZF_ALT_C_OPTS="--preview='ls -la'"
# ---------- Fzf: load shell integration ----------
eval "$(fzf --bash)"Tip
Order matters: all the exports must finish before the eval "$(fzf --bash)" line. The integration script reads variable values when it loads, so variables defined afterwards will have no effect on those keybindings.
One management key: because CTRL-R only has OPTS (no COMMAND), and its data source is already fixed, don't waste time looking for a variable that doesn't exist. The FZF_<CONTEXT>_<ROLE> naming pattern immediately tells you what can be changed.
Episode 8 made your fzf configuration centralized and systematic: FZF_DEFAULT_COMMAND for the global data source, FZF_DEFAULT_OPTS for global settings, per-keybinding variables (FZF_CTRL_T_*, FZF_CTRL_R_OPTS, FZF_ALT_C_*) for specific adjustments, FZF_DEFAULT_OPTS_FILE for long options, and a tidy config block pattern.
The key takeaways:
FZF_DEFAULT_* applies globally; per-keybinding variables add on top of it.COMMAND sets the data source; OPTS sets options; CTRL-R only has OPTS.In episode 9, we will cover fuzzy completion (**<TAB>) — how fzf completes commands, paths, processes, and even environment variables, plus building your own custom completer. See you in episode 9!