Connecting keys and events to fzf actions such as accept, execute, execute-silent, become, reload, change-preview-window, select-all, and toggle+ via --bind. Including multi-action chains with the plus sign, per-event conditional bindings, --expect to capture the last key, --no-clear, and real use cases: opening editors, git add, and killing processes.

In episode 14 you learned to treat fzf items as data that must not be executed carelessly. Now it's the reverse: you will deliberately bind that data to actions. --bind is the true heart of fzf — it turns a search tool into an interface where you select, then run something without ever touching the keyboard to type a full command.
This episode covers the anatomy of --bind, the core actions (accept, execute, execute-silent, become, reload, change-preview-window, preview-top, select-all, toggle+), how to chain several actions at once with the + sign, per-event conditional bindings, --expect for capturing the last key pressed, --no-clear, and three real use cases: opening an editor, git add, and killing processes.
Think of --bind as the keypad in a cockpit: the same key can trigger one motion, and a sequence of keys can be programmed to trigger a sequence of motions. It all starts from one small sentence.
--bind--bind accepts a comma-separated list of KEY:ACTION expressions. The triggers aren't just physical keys — there are also events fired by fzf's own conditions, like start (when fzf starts), load (when the list has finished loading), change (when the query changes), focus (when the cursor moves to an item), up to one and zero (when the results leave one or zero items).
fzf --bind 'ctrl-j:down,ctrl-k:up'
fzf --bind 'ctrl-r:reload(ls -la)'
fzf --bind 'change:reload(rg -l {q})'acceptaccept is the most basic action: accept the current selection and exit fzf — this is what's bound to the Enter key by default. It deserves to be mentioned first because almost every other action is designed to lead you here. Its sibling, accept-non-empty, prevents fzf from exiting without a selection — useful when canceling is far better than accepting an empty result.
fzf --bind 'enter:accept-non-empty'execute, execute-silent, becomeThese are the three actions that turn fzf from a list into a command executor. All three replace {} with the currently highlighted item:
execute(command) — runs the command, shows the output on screen, then returns to fzf.execute-silent(command) — the same, but the output isn't shown; good for actions whose success doesn't need to be seen.become(command) — runs the command and replaces fzf completely: the fzf process ends, replaced by a new process.fzf --bind 'enter:execute(less {})'fzf --bind 'enter:become(vim {})'Tip
Choose execute when you want to return to the list afterwards (for example, peeking at a file), and become when you want fzf to become that program (for example, opening an editor on the selected file). Use execute-silent for actions that shouldn't display anything — git add or kill calls are ideal candidates.
reloadreload(command) replaces the currently displayed item list with the output of a new command, without exiting fzf. This is the foundation of living lists — we'll dissect it deeper in episode 16, but here's a short example: reloading the process list with one key press.
ps -ef | fzf --bind 'ctrl-r:reload(ps -ef)'change-preview-window and preview-topSometimes the preview doesn't have to stay on the right. change-preview-window(...) moves and resizes the preview on the spot, and with several sets separated by |, one key can cycle its position. preview-top scrolls the preview all the way to the top — perfect when the file being viewed is long and you want to return to the beginning without scrolling manually.
fzf --preview 'bat --color=always {}' \
--bind 'ctrl-/:change-preview-window(right|down|hidden)'fzf --preview 'bat --color=always {}' --bind 'alt-t:preview-top'select-all and toggle+These actions fill in the other side: handling many items at once. select-all marks the entire current filter result — not just what's visible on screen. toggle+ (full name toggle+down, bound to Tab by default) marks the active item then moves down — giving the feel of "marking one by one, downward".
seq 20 | fzf --multi --bind 'ctrl-a:select-all+accept'--expect: Capturing the Last KeyNormally fzf only tells you which item was selected, not which key closed it. --expect=KEY changes that: the named key is also "recorded", and fzf prints it as the first line of output, followed by the selection on the next line.
printf '%s\n' edit.pdf report.pdf | fzf --expect=ctrl-oThe output looks something like this:
ctrl-o
edit.pdfA reading script then checks the first line to decide the action — a kind of handshake protocol between fzf and the script:
read -r key < <(printf '%s\n' edit.pdf report.pdf | fzf --expect=ctrl-o)
read -r item
case "$key" in
ctrl-o) open "$item" ;;
*) echo "dibatalkan: $item" ;;
esac--no-clear: Leaving a Trace on ScreenBy default fzf clears its interface when done — the screen returns to what it was before fzf appeared. --no-clear disables that: the last result stays visible after exiting. Its main use is reducing screen flicker when an application calls fzf several times in a row, or when you genuinely want the search result to remain readable after selecting.
foo=$(seq 100 | fzf --no-clear)Warning
--no-clear keeps the last screen contents stuck — including the preview contents. If the preview displays sensitive data (remember episode 14), that trace can be read by whoever comes later. Use --no-clear for workflows that genuinely need it, and be aware that it's an unintentional visual reminder that can become a data trail.
Two magic keys extend --bind: + chains several actions in one expression, and events make bindings run conditionally. Chains run in order, left to right:
git ls-files -m -o --exclude-standard | fzf --multi \
--bind 'ctrl-a:select-all' \
--bind 'enter:execute-silent(git add {+})+reload(git ls-files -m -o --exclude-standard)'To change a key's behavior dynamically — a kind of "mode" — fzf provides toggle-bind(KEY): the key passed as an argument is toggled on and off alternately. Combine it with event bindings, and you can turn off reload when it isn't needed:
fzf --bind 'alt-b:toggle-bind(ctrl-r)' \
--bind 'ctrl-r:reload(ps -ef)' \
--bind 'change:reload(ps -ef)'With alt-b, you can disable automatic reload while viewing a calm list, then turn it back on. The change, focus, one, and zero events give the same conditional dimension to actions that should only happen under certain conditions.
Finally, three workflows you'll feel at your daily desk:
Open the selected file in an editor — fzf is replaced by the editor, {} becomes its argument:
fzf --bind 'enter:become($EDITOR {})'git add without leaving fzf — mark many files, add them to staging, the list reloads:
git ls-files -m -o --exclude-standard | fzf --multi \
--bind 'ctrl-a:select-all' \
--bind 'enter:execute-silent(git add {+})+reload(git ls-files -m -o --exclude-standard)'Kill the selected process — the PID is taken from the first field ({1}), the action runs without displaying anything:
ps -eo pid,comm | fzf --bind 'enter:execute-silent(kill {1})'| Mistake | Symptom | Solution |
|---|---|---|
Forgetting to quote {} in an action | Injection or wrong command | Quote the placeholder as in episode 14 |
Using execute when you want become | Always returns to the list | become to replace the fzf process |
Putting + at the start of a chain | Parse error | Chains start from the first action after : |
--expect ignored | No key line in the output | --expect adds a first line; read it with sequential read |
Reload on change too aggressive | List flickers on every keystroke | Limit it with sleep inside the reload command |
{+} on space-containing file names | Arguments split | Use {+f} or the null delimiter depending on context |
Caution
A + chain executes in order, and actions that end the fzf process (accept, become, abort) stop the rest of the chain. Order deliberately: put actions that must finish before fzf closes at the front, and closing actions at the very back.
In this episode 15 you took full control of fzf's keys and actions: the anatomy of --bind with both keys and events as triggers; the core actions accept, execute, execute-silent, become, reload, change-preview-window, preview-top, select-all, and toggle+; chaining several actions with +; conditional bindings via events and toggle-bind; --expect turning the last key into data; --no-clear; and three real use cases from opening an editor, bulk git add, to killing processes.
The message to take home: --bind turns fzf from a selecting tool into an acting tool — list, select, then execute, all in one motion.
But so far you've only touched the reload action in passing. What if the reloaded list isn't just a refresh, but a stream of data that keeps changing, controlled even by processes outside fzf? In episode 16 we open a new chapter: dynamic reload & HTTP API — living lists via --reload, --change, and periodic scheduling, cross-reload tracking with --track and --id-nth, and full control of fzf from another program via --listen.