Open-Source Wikis

/

fzf

/

Features

/

Listen mode

junegunn/fzf

Listen mode

--listen [ADDR] starts an HTTP server inside fzf so external programs can drive its UI. This is the foundation for interactive ripgrep filters, kubectl browsers, and any tool that wants fzf's UI but its own data flow.

Quick start

# Terminal A: start fzf with a listen server
seq 100 | fzf --listen 8123

# Terminal B: change the prompt and the query
curl -X POST -d 'change-prompt(>> )+change-query(42)' localhost:8123

# Terminal B: get the current first 5 visible items
curl 'localhost:8123/?limit=5'

Address format

parseListenAddress in src/server.go accepts:

  • <port> (e.g. 8123) — bind on localhost.
  • <host>:<port> — explicit host. Only localhost / 127.0.0.1 is allowed unless FZF_API_KEY is set.
  • <path>.sock — Unix domain socket. The file is created with mode 0600.
  • Empty (--listen) — bind on localhost and a random port. The chosen port is printed when the server starts.

Authentication

For non-localhost TCP binds, FZF_API_KEY is required. Every request must include X-API-Key: <value>. The comparison uses subtle.ConstantTimeCompare. For Unix sockets and localhost, the API key is optional.

Verbs

POST / — execute actions

The body is a single fzf action list, using the same syntax as --bind arguments:

change-prompt(picked> )+reload(ls)+first

The body is parsed by the same parseSingleActionList machinery as --bind, so anything you can do with a key binding you can do over HTTP.

A Content-Length header is required. Bodies above 1 MiB are rejected with 400 Bad Request. Successful posts return 200 OK with no body.

GET / — fetch state

GET /?limit=100&offset=0

Returns the current visible match list as JSON (the count, the query, and the items, including any --with-nth transformations). Both limit (default 100) and offset (default 0) are accepted.

If the terminal cannot service the GET within 2 seconds, fzf returns 503 Service Unavailable — meaning the action queue is full or the renderer is paused.

Status codes

Code Reason
200 OK Success. JSON body for GETs.
400 Bad Request Bad method, malformed action, missing/invalid Content-Length.
401 Unauthorized Missing or wrong API key.
503 Service Unavailable Action queue full or GET handler timed out.

Why no net/http?

The comment at the top of src/server.go records the binary-size impact:

* No --listen:            2.8MB
* --listen with net/http: 5.7MB
* --listen w/o net/http:  3.3MB

Hand-rolling the HTTP parser saves ~2.4 MB compared to the stdlib. The parser handles only what fzf needs: GET / POST methods, Content-Length, X-API-Key, and a JSON or plain-text body.

Programmatic clients

  • The README documents a few examples in the "Listen mode" section.
  • The companion junegunn/fzf.vim plugin uses --listen for some live-filter pickers.
  • Any HTTP client works. There's no streaming/long-poll protocol; clients poll GET / if they need to track changes.

Where it lives

Concern File
Server src/server.go
Address parsing src/server.go (parseListenAddress)
Action parsing src/options.go (parseSingleActionList)
Action injection into Terminal src/terminal.go (actionChannel)
GET handler src/terminal.go (getHandler)
API key env var FZF_API_KEY
Tests test/test_server.rb

See systems/http-server for a deeper walkthrough.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Listen mode – fzf wiki | Factory