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,--fishflags that emit shell scripts - Go library (unofficial/internal): exported symbols in
package fzfwith 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=valueand--opt valuestyles- 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-clearetc.
Server:
--listen[=ADDR]— enable HTTP control plane (safe mode: localhost only)--listen-unsafe[=ADDR]— allow remote access withoutFZF_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 commandFZF_DEFAULT_OPTS— default options stringFZF_DEFAULT_OPTS_FILE— path to options fileFZF_API_KEY— API key for non-local--listenendpoints
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_PORTfor scripts to discover
Authentication#
- Non-local addresses require
FZF_API_KEYenv var - Sent as
X-Api-KeyHTTP 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-sortResponse:
200 OK— actions dispatched to Terminal’sactionChannel400 Bad Request— malformed action syntax or missing body401 Unauthorized— invalid API key503 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#
| Binding | Action | Env var to customize |
|---|---|---|
Ctrl-T | Paste selected files into command line | FZF_CTRL_T_COMMAND, FZF_CTRL_T_OPTS |
Ctrl-R | Search shell command history | FZF_CTRL_R_COMMAND, FZF_CTRL_R_OPTS |
Alt-C | cd into selected directory | FZF_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 pickerCustomizable 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/)#
| Export | Signature | Purpose |
|---|---|---|
Run | func Run(opts *Options) (int, error) | Main entry point; blocks until exit |
ParseOptions | func ParseOptions(useDefaults bool, args []string) (*Options, error) | Parse flags/env into Options struct |
BuildPattern | func BuildPattern(cache, patternCache, fuzzy, algo, ...) *Pattern | Compile a search pattern |
NewChunkList | func NewChunkList(cache *ChunkCache, trans ItemBuilder) *ChunkList | Create item store |
NewMatcher | func NewMatcher(cache, patternBuilder, ...) *Matcher | Create search engine |
NewReader | func NewReader(pusher func([]byte) bool, eventBox, ...) *Reader | Create input reader |
NewTerminal | func NewTerminal(opts, eventBox, executor) (*Terminal, error) | Create TUI |
package fzf/src/algo#
| Export | Purpose |
|---|---|
FuzzyMatchV2 | Smith-Waterman fuzzy match with bonus scoring |
FuzzyMatchV1 | Original DP fuzzy match |
ExactMatchNaive | Exact substring match |
PrefixMatch, SuffixMatch, EqualMatch | Boundary matchers |
Init(scheme string) bool | Initialize 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
*Optionsstruct - No versioning strategy — module is at
github.com/junegunn/fzf(v0-style; nov2path) - 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#
No net/http dependency — the custom HTTP parser in
server.gois 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.Actions as a DSL — the ~200 action types, chainable with
+, form a mini programming language for TUI automation. Thetransform(CMD)action (run shell → output is more actions) makes it Turing-complete in practice. This same DSL works identically in--bindflags and HTTP POST bodies — a clean unification.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).Zero-copy GET response —
dumpStatus()acquires the Terminal mutex, serializes directly to JSON, and returns without copying items. Thelimit/offsetparams allow paginating large result sets without allocating the full slice.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 customizeFZF_CTRL_T_COMMANDetc. rather than writing Go code. The real API boundary is a bash function, not a Go interface.