fzf — API Surface#

API types#

  • CLI (primary): custom flag parser, ~177 flags, no subcommands
  • HTTP REST (optional control plane): minimalist custom server enabled via --listen
  • Shell integration: --bash, --zsh, --fish flags that emit shell scripts
  • Go library (unofficial/internal): exported symbols in package fzf with no stability guarantee

No gRPC, no plugin system, no proto files.


CLI#

Framework#

Custom flag parser — defined in src/options.go:parseOptions(). fzf does not use flag, cobra, urfave/cli, or any third-party library. The parser is hand-rolled to support:

  • --opt=value and --opt value styles
  • Boolean negation: --no-opt (present for most flags)
  • Numeric short flags: --1, +1 (for --select-1, --exit-0)
  • Reading options from env vars (FZF_DEFAULT_OPTS, FZF_DEFAULT_OPTS_FILE)

Command structure#

fzf has no subcommands. The single binary accepts exactly one command structure:

fzf [OPTIONS]

The only “sub-commands” are special --print-* output flags:

  • --bash — print Bash shell integration script (key bindings + completions)
  • --zsh — print Zsh shell integration script
  • --fish — print Fish shell integration script
  • --man — print man page
  • --version — print version

Flag patterns (177 flags total)#

Flags are grouped into functional categories:

Search behavior:

  • --query=STR — initial query string
  • --filter=STR — non-interactive filter mode (streaming)
  • --algo={v1|v2} — fuzzy match algorithm
  • --scheme={default|path|history} — scoring scheme
  • --extended, --extended-exact — extended-search mode
  • --case-sensitive, --ignore-case, --smart-case — case handling
  • --literal, --normalize — input normalization
  • --nth=N[,N], --with-nth=N[,N] — field limiting
  • --delimiter=STR — field delimiter

Input/output:

  • --read0, --print0 — NUL-delimited I/O
  • --print-query — print query string before results
  • --expect=KEY[,KEY] — report which key triggered acceptance
  • --tac — reverse input order
  • --tail=N — keep only last N items

Selection:

  • --multi, --multi=N — enable multi-select (with optional limit)
  • --select-1 (--1), --exit-0 (--0) — auto-select when single/zero matches

UI layout (extensive):

  • --layout={default|reverse|reverse-list}
  • --height=N% / --min-height=N
  • --border[=SHAPE], --list-border, --header-border, --input-border, --footer-border
  • --margin, --padding
  • --style={full|default|minimal} — compound style preset

Preview:

  • --preview=CMD — preview command (shell, supports {}/{n} placeholders)
  • --preview-window=OPTS — size, position, wrap, border

Key bindings and actions:

  • --bind=KEY:ACTION[+ACTION]... — bind keys to action chains
  • --unbind=KEY, --no-clear etc.

Server:

  • --listen[=ADDR] — enable HTTP control plane (safe mode: localhost only)
  • --listen-unsafe[=ADDR] — allow remote access without FZF_API_KEY

Integration:

  • --tmux[=OPTS], --zellij — spawn in a tmux/zellij popup (re-exec)
  • --with-shell=CMD — override shell for subprocess execution
  • --walker=FLAGS, --walker-root=DIR, --walker-skip=DIRS — built-in filesystem walker

Env var bindings (parallel to flags):

  • FZF_DEFAULT_COMMAND — default input command
  • FZF_DEFAULT_OPTS — default options string
  • FZF_DEFAULT_OPTS_FILE — path to options file
  • FZF_API_KEY — API key for non-local --listen endpoints

HTTP REST API (--listen)#

Design rationale#

The server is deliberately not using net/http to minimize binary size:

  • Without --listen: 2.8 MB
  • With net/http: 5.7 MB
  • With custom server: 3.3 MB

The implementation in src/server.go parses raw TCP bytes, handling headers and body manually with a bufio.Scanner custom split function.

Transport#

  • TCP: --listen=HOST:PORT (default localhost:0 = auto-assign port)
  • Unix domain socket: --listen=/path/to/file.sock (chmod 0600)
  • Port 0 = OS assigns; fzf writes the actual port to $FZF_PORT for scripts to discover

Authentication#

  • Non-local addresses require FZF_API_KEY env var
  • Sent as X-Api-Key HTTP header
  • Validated with crypto/subtle.ConstantTimeCompare (timing-safe)
  • Local addresses (localhost, 127.0.0.1, Unix sockets) bypass auth by default

Endpoints#

GET / — Query current state#

Request: GET /?limit=N&offset=N HTTP/1.1

Response: JSON object (Content-Type: application/json)

{
  "reading": true,
  "progress": 50,
  "query": "current query string",
  "position": 3,
  "sort": true,
  "totalCount": 1000,
  "matchCount": 42,
  "current": { "index": 2, "text": "selected item text" },
  "matches": [ { "index": N, "text": "..." }, ... ],
  "selected": [ { "index": N, "text": "..." }, ... ]
}

Query params: limit (default 100) and offset (default 0) for pagination of matches and selected arrays.

Returns HTTP 503 with {"error":"timeout"} if the Terminal mutex cannot be acquired within 2 seconds.

POST / — Dispatch actions#

Request: POST / HTTP/1.1 with plain-text body containing action strings

Body format: One or more fzf actions, same syntax as --bind values:

change-query(new search term)
execute(echo hello)
reload(find . -name '*.go')

Actions can be chained with +:

change-query(foo)+refresh-preview+toggle-sort

Response:

  • 200 OK — actions dispatched to Terminal’s actionChannel
  • 400 Bad Request — malformed action syntax or missing body
  • 401 Unauthorized — invalid API key
  • 503 Service Unavailable — Terminal busy (2-second timeout)

Action vocabulary: ~200 action types, including:

  • Query manipulation: change-query, clear-query, put
  • Selection: select-all, deselect-all, toggle, toggle-all
  • Navigation: up, down, page-up, page-down, first, last, jump
  • Input source: reload(CMD), reload-sync(CMD)
  • Preview: change-preview(CMD), preview-up, refresh-preview, toggle-preview
  • Execution: execute(CMD), execute-silent(CMD), become(CMD)
  • UI: change-prompt(STR), change-header(STR), change-border-label(STR)
  • Control: abort, accept, close, bell
  • Transform: transform(CMD) — run a shell command and interpret output as actions
  • Async variants: bg-transform(CMD) — run without blocking the UI

Shell integration#

Activated via eval "$(fzf --bash)" (or --zsh, --fish) in shell rc files.

Key bindings installed#

BindingActionEnv var to customize
Ctrl-TPaste selected files into command lineFZF_CTRL_T_COMMAND, FZF_CTRL_T_OPTS
Ctrl-RSearch shell command historyFZF_CTRL_R_COMMAND, FZF_CTRL_R_OPTS
Alt-Ccd into selected directoryFZF_ALT_C_COMMAND, FZF_ALT_C_OPTS

Tab completion#

Triggered via **<Tab> suffix in shell commands:

vim **<Tab>           # file picker
ssh **<Tab>           # host picker from /etc/hosts
kill -9 **<Tab>       # process picker

Customizable via _fzf_compgen_path() and _fzf_compgen_dir() shell functions.


Go library API (informal)#

fzf is not designed as a library and documents no stable public API, but its exported symbols are usable by Go programs:

package fzf (src/)#

ExportSignaturePurpose
Runfunc Run(opts *Options) (int, error)Main entry point; blocks until exit
ParseOptionsfunc ParseOptions(useDefaults bool, args []string) (*Options, error)Parse flags/env into Options struct
BuildPatternfunc BuildPattern(cache, patternCache, fuzzy, algo, ...) *PatternCompile a search pattern
NewChunkListfunc NewChunkList(cache *ChunkCache, trans ItemBuilder) *ChunkListCreate item store
NewMatcherfunc NewMatcher(cache, patternBuilder, ...) *MatcherCreate search engine
NewReaderfunc NewReader(pusher func([]byte) bool, eventBox, ...) *ReaderCreate input reader
NewTerminalfunc NewTerminal(opts, eventBox, executor) (*Terminal, error)Create TUI

package fzf/src/algo#

ExportPurpose
FuzzyMatchV2Smith-Waterman fuzzy match with bonus scoring
FuzzyMatchV1Original DP fuzzy match
ExactMatchNaiveExact substring match
PrefixMatch, SuffixMatch, EqualMatchBoundary matchers
Init(scheme string) boolInitialize scoring scheme

package fzf/src/util#

Core utilities: EventBox, Executor, Chars, Slab, AtomicBool, ConcurrentSet

API style#

  • Struct-based construction — each component is created via a New*() constructor receiving explicit dependencies
  • No functional options — configuration goes through the monolithic *Options struct
  • No versioning strategy — module is at github.com/junegunn/fzf (v0-style; no v2 path)
  • No import stability guarantees — internal types (Item, Chunk, Result) are exported but can change

Plugin / Extension system#

fzf has no plugin system in the traditional sense, but provides several extension points:

1. --bind KEY:execute(CMD) — shell escape hatch#

Any key binding can shell out to an arbitrary command. The command receives the current item as {} and selected items as {+}. This is the primary extension mechanism.

2. --preview CMD — live preview panel#

A shell command that receives {} and renders its stdout in a side panel. Supports ANSI colors and size-adaptive output via $FZF_PREVIEW_COLUMNS / $FZF_PREVIEW_LINES.

3. transform(CMD) action — programmable control flow#

A shell command that returns fzf action strings. This allows dynamic behavior: change the preview command based on the currently focused item, reload from a different source based on a condition, etc. This is fzf’s most powerful extension point — it turns key bindings into a small programming language.

4. HTTP control plane (--listen) — external automation#

External processes (editors, scripts, daemons) can:

  • Query fzf state via GET /
  • Drive fzf programmatically via POST / with action strings
  • Integrate fzf as an interactive UI component from a shell script or editor plugin

5. --walker — filesystem input customization#

--walker=file,dir,follow,hidden flags control the built-in fastwalk-based filesystem walker, replacing the need for an external find command for common use cases.


Notable API design observations#

  1. No net/http dependency — the custom HTTP parser in server.go is explicitly motivated by binary size. At 2.7 KB of code it handles the full HTTP/1.1 request-response cycle for the two endpoints. A rare and deliberate tradeoff.

  2. Actions as a DSL — the ~200 action types, chainable with +, form a mini programming language for TUI automation. The transform(CMD) action (run shell → output is more actions) makes it Turing-complete in practice. This same DSL works identically in --bind flags and HTTP POST bodies — a clean unification.

  3. CLI flag count (177) without a framework — fzf’s hand-rolled parser handles a flag surface that would typically justify cobra or urfave/cli. The result is a binary with no CLI framework dependency and full control over exotic flag syntax (--no-*, --1, env var layering).

  4. Zero-copy GET responsedumpStatus() acquires the Terminal mutex, serializes directly to JSON, and returns without copying items. The limit/offset params allow paginating large result sets without allocating the full slice.

  5. Shell integration as the primary “SDK” — fzf’s most widely used API is the shell key bindings and **<Tab> completion. This is “configuration as extension”: users customize FZF_CTRL_T_COMMAND etc. rather than writing Go code. The real API boundary is a bash function, not a Go interface.