How to Implement Dynamic List Reloading in fzf: A Complete Guide

You can implement dynamic list reloading in fzf by binding the reload or reload-sync action to events like start, change, or key presses, which executes a command and replaces the candidate list without restarting the UI.

The junegunn/fzf command-line fuzzy finder supports dynamic list reloading, allowing you to update candidates on-the-fly based on user input or external events. This capability leverages the reload and reload-sync actions to execute arbitrary shell commands and refresh the internal list without disrupting the interactive interface.

Understanding the reload Action Architecture

Action Binding and Parsing

When you pass --bind '<event>:reload:<command>' on the command line, fzf parses this through parseActionList in src/options.go (lines 2030-2032). The parser identifies the action type as actReload or actReloadSync and stores the associated command string for later execution.

Initial Reload on Start

If you bind the reload action to the start event, fzf handles this specially through extractReloadOnStart in src/options.go (lines 3895-3899). This function removes the binding from the keymap and returns the command string so it can execute before the UI appears. In src/core.go (lines 70-78), the initial reload runs while the terminal is being created, ensuring the list is populated immediately.

Event Loop Execution

Inside the main event loop (Terminal.run in src/terminal.go), every incoming key event translates into a list of actions. When the loop encounters actReload or actReloadSync (lines 7267-7284), it enters a specific branch that prepares the command for execution.

Command Building and Placeholder Substitution

The terminal calls t.buildPlusList(a.a, false) to construct the final command string. This process handles placeholder substitution through replacePlaceholder, supporting:

  • {q} – the current query string
  • {} – the selected line

Temporary files may be created for multi-line selections, with cleanup handled via defer removeFiles(temps) in src/core.go (lines 78-81).

Synchronous vs Asynchronous Reload

The implementation distinguishes between two modes:

  • reload – Sets reloadSync = false. The command runs asynchronously, allowing the UI to remain responsive and accept further input while the list rebuilds.
  • reload-sync – Sets reloadSync = true (line 7281 in src/terminal.go). The UI blocks until the command finishes, ensuring the displayed list reflects the latest reload result before accepting subsequent actions.

Practical Implementation Examples

Reload on Start (Initial List Population)

Use the start event to populate the list when fzf launches:

fzf --bind 'start:reload:git ls-files' \
    --height 40% --layout reverse

This executes git ls-files immediately, filling the interface with repository files before user interaction begins.

Reload on Query Change (Interactive Filtering)

Implement real-time search by binding to the change event:

RG_PREFIX='rg --column --line-number --no-heading --color=always --smart-case'
fzf --bind "start:reload:$RG_PREFIX ''" \
    --bind "change:reload:$RG_PREFIX {q} || true" \
    --ansi --disabled \
    --height 50% --layout reverse

Each keystroke triggers a new ripgrep search, with {q} substituted for the current query. The || true ensures fzf continues running even if the search returns no results.

Reload on Key Press (Manual Refresh)

Allow users to manually refresh the list with a keyboard shortcut:

fzf --bind 'ctrl-r:reload:ps -ef' \
    --header 'Press CTRL-R to reload' \
    --height 60% --layout reverse

Pressing Ctrl-R re-executes ps -ef and updates the process list dynamically.

Synchronous Reload with reload-sync

Use reload-sync when the command must complete before further interaction:

fzf --bind 'ctrl-r:reload-sync:sudo apt update && apt list --upgradable' \
    --height 70% --layout reverse

The UI blocks until apt update finishes, ensuring the upgradable package list reflects the latest repository state.

Key Source Files and Functions

File Role Key Lines
src/options.go Parses command-line bindings and identifies reload actions via parseActionList; extracts initial reload commands through extractReloadOnStart. 2030-2032, 3895-3899
src/terminal.go Implements the main event loop (Terminal.run) and handles actReload/actReloadSync execution, including command building with buildPlusList and placeholder substitution. 7267-7284
src/core.go Manages initial reload execution during terminal creation and handles temporary file cleanup. 70-78, 78-81

Summary

  • Dynamic list reloading in fzf uses the reload or reload-sync actions to execute commands and refresh candidates without restarting the UI.
  • Placeholder substitution supports {q} for the current query and {} for selected items, processed through replacePlaceholder in the terminal.
  • Asynchronous reloading (reload) keeps the UI responsive during command execution, while synchronous reloading (reload-sync) blocks until completion.
  • Event binding options include start (initial load), change (query updates), and key presses for manual control.
  • Core implementation spans src/options.go for parsing, src/terminal.go for execution, and src/core.go for initialization.

Frequently Asked Questions

What is the difference between reload and reload-sync in fzf?

The reload action runs commands asynchronously, allowing you to continue typing or navigating while the list updates in the background. In contrast, reload-sync blocks the UI until the command completes, ensuring the candidate list reflects the latest state before accepting further input. According to the source code in src/terminal.go (line 7281), the only difference is the reloadSync boolean flag that determines whether the terminal waits for command completion.

How do I pass the current query to the reload command?

Use the {q} placeholder in your command string. When the reload action triggers, fzf substitutes {q} with the current query text through the replacePlaceholder function. For example: --bind "change:reload:rg --column --line-number {q}" executes ripgrep with the typed query on every keystroke. This placeholder works with both reload and reload-sync actions.

Can I use reload with multiple placeholder variables?

Yes, you can combine {q} (current query) with {} (selected line) and other placeholders in the same reload command. The buildPlusList function in src/terminal.go processes these placeholders before execution. For instance, --bind "ctrl-e:reload:cat {} | grep {q}" uses the selected file content and current query simultaneously. Note that using {} requires a selection to exist; otherwise, the placeholder expands to an empty string.

Why does my reload action fail when the command returns no results?

fzf treats command failure (non-zero exit code) as an error condition that may interrupt the reload process. To prevent this, append || true to your command, which ensures the exit code is always zero. For example: --bind "change:reload:rg {q} || true" allows fzf to continue operating even when ripgrep finds no matches. This pattern is particularly important for dynamic filtering where empty result sets are expected behavior.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →