fx: Interactive Terminal JSON Viewer (Alternative to jq for Exploration)
Last updated:
fx is an interactive terminal JSON viewer — you pipe a document into it (cat data.json | fx), and fx opens a full-screen TUI where arrow keys navigate the tree, slash searches, and a dot key opens a filter line that accepts plain JavaScript. It fills a gap that jq does not: exploring an unfamiliar JSON shape before you know what to query. fx 35+ (current as of 2026) is written in Go and ships as a single static binary — no Node.js required. The same tool also works as a non-interactive pipe (cat data.json | fx '.users[0]'), so the JavaScript filter you developed by clicking around becomes the one-liner you drop into a shell script or CI step. fx is one of three popular terminal JSON tools alongside jless (read-only viewer) and gron (flatten for grep), each with a different sweet spot.
Need to inspect JSON without leaving the browser? Jsonic's JSON Validator catches syntax errors before fx or jq ever sees them — paste a payload and get exact-line diagnostics.
Open JSON ValidatorWhat fx is and when it beats jq for exploration
fx is built for the moment when you have a JSON document in front of you and do not yet know what is in it. You ran a curl against a new API, dumped a Kubernetes resource, or grabbed a webhook payload from a log — the document is large, the shape is unfamiliar, and writing a jq filter blind feels like guessing. fx replaces that guess with a navigable view: arrow keys move between fields, Enter folds and unfolds nodes, slash searches across keys and values, and the JSON path of the currently focused node is always visible at the bottom of the screen.
Where jqshines is the opposite case — you already know the query, the document shape is stable, and you need to run the same transformation across many records or as part of a shell pipeline. fx is not a replacement; it is a complement. The two coexist on most engineers' machines. A common flow looks like this: open the document in fx, navigate to interesting nodes, write a JavaScript filter interactively until the result panel shows what you want, then convert that filter to an equivalent jq expression for the production script.
For the jq half of that workflow, our jq examples and the jq cheat sheet cover the recipes you reach for most often. fx is the discovery tool; jq is the production tool.
Installation: brew, npm, scoop, snap, Go binary
fx ships through every major package manager. The Go rewrite means the binary is a single static executable — there is no Node, Python, or shared-library dependency to worry about, which makes it easy to drop into a Docker image or a CI runner.
# macOS / Linux (Homebrew)
brew install fx
# Cross-platform via npm (wraps the Go binary)
npm install -g fx
# Windows (Scoop)
scoop install fx
# Windows (Chocolatey)
choco install fx
# Linux (Snap)
sudo snap install fx
# Linux (Go install — for users with a Go toolchain)
go install github.com/antonmedv/fx@latest
# Static binary — drop into /usr/local/bin or a Dockerfile
curl -L https://github.com/antonmedv/fx/releases/latest/download/fx_linux_amd64 -o /usr/local/bin/fx
chmod +x /usr/local/bin/fxVerify the install:
$ fx --version
35.0.0
$ echo '{"hello": "world"}' | fx
# (TUI opens — press q to quit)The npm package is the simplest path on a workstation that already has Node available — it pulls the appropriate Go binary for the current platform and exposes it on $PATH. For CI images, prefer the static binary download: it adds about 5 MB to the image and avoids pulling Node just to ship one tool. The legacy Node.js implementation is still on npm as @medv/fx if you have scripts that depend on its JavaScript API.
Interactive mode: navigation, search, filtering
Launch fx by piping any JSON into it. The TUI splits into a tree view (the document) and a status line (the current JSON path plus the filter input when active). The keybindings borrow from vim and from less, so muscle memory transfers.
# Launch on a file
cat data.json | fx
# Launch on a URL response
curl -s https://api.example.com/users | fx
# Launch on a kubectl resource
kubectl get pod my-pod -o json | fxThe keys you use most:
↑ ↓ ← →orh j k l— move the cursorEnterorSpace— expand or collapse the focused nodee— expand the entire treec— collapse the entire tree/— start a search; type the query, press Enter, thennandNto jump between matches.— open the filter line (JavaScript expression, document bound tox)y— copy the currently focused node as JSON to the clipboardp— copy the JSON path to the focused node (e.g.,.users[3].email)g/G— jump to top / bottomq— quit
Search is case-insensitive by default and matches both keys and values. Filter mode (the . key) is the differentiator — it accepts a full JavaScript expression and re-renders the right-hand panel as you type, so refining a query feels like REPL coding rather than recompiling a regex. Pair this with the path-copy shortcut: navigate to the deeply nested field you care about, press p, paste the path into a script as a starting point.
Non-interactive mode: pipes and one-off queries
When fx is invoked with a positional argument, or when stdout is not a terminal, it skips the TUI and behaves like a non-interactive filter. This is what lets the same binary serve both desk-side exploration and CI scripts.
# One-off path query
cat data.json | fx '.users[0].name'
# JavaScript filter
cat data.json | fx 'x => x.users.filter(u => u.age > 30)'
# Chain into another command — fx detects no TTY
cat data.json | fx '.items.length' | tee count.txt
# Multiple expressions evaluated left to right
cat data.json | fx '.users' 'x => x.map(u => u.email)'
# Pretty-print only (no filter argument, but no TTY either)
cat data.json | fx | head -50The argument syntax has two forms. A leading dot (.users[0].name) is a short-hand path expression — fx parses it and walks the document. A full JavaScript arrow function (x => x.users.filter(...)) gives you the full expressive power of JS, including chained map/filter/reduce calls, template literals, and helper functions you define in ~/.fxrc.js. Multiple arguments compose left to right — the output of one becomes the input of the next, which keeps individual expressions short.
For pipelines that mix tools, fx slots in alongside jq, grep, and awk. Our guides on curl + JSON and JSON CLI formatting show how these compose into useful one-liners.
JavaScript-based filters (vs jq's DSL)
fx's filter language is JavaScript. The current document is bound to a variable named x, and any expression that evaluates to a JSON-serializable value (or to a function called with x) becomes the new view. Arrow functions, destructuring, optional chaining, nullish coalescing, and the standard array methods all work.
# Path expression (shorthand)
cat data.json | fx '.users[0]'
# Same query as a full JS arrow function
cat data.json | fx 'x => x.users[0]'
# Filter an array
cat data.json | fx 'x => x.users.filter(u => u.age > 30)'
# Map + reduce — total revenue across orders
cat data.json | fx 'x => x.orders.reduce((sum, o) => sum + o.total, 0)'
# Destructure and reshape
cat data.json | fx 'x => x.users.map(({id, email, name}) => ({id, email, name}))'
# Group by a key (one-liner)
cat data.json | fx 'x => Object.groupBy(x.events, e => e.type)'
# Pretty-print sorted keys
cat data.json | fx 'x => Object.fromEntries(Object.entries(x).sort())'Comparing the same query against jq:
| Goal | fx (JavaScript) | jq (DSL) |
|---|---|---|
| First user's name | .users[0].name | .users[0].name |
| Adults only | x => x.users.filter(u => u.age > 30) | .users[] | select(.age > 30) |
| Just the emails | x => x.users.map(u => u.email) | .users[].email |
| Count items | x => x.items.length | .items | length |
| Sum a field | x => x.orders.reduce((s, o) => s + o.total, 0) | [.orders[].total] | add |
Neither language is strictly better — they trade off familiarity (JS) against compactness (jq). For developers who write JavaScript daily, fx eliminates a context switch. For shell-heavy workflows where everyone on the team knows jq, sticking with the DSL keeps scripts consistent.
Custom themes and ~/.fxrc.js configuration
fx reads ~/.fxrc.js on startup. The file is plain JavaScript and lets you set a theme, define helper functions that become available inside filters, and configure default behaviors. Because it is JavaScript and not a TOML or JSON config, you can compute values from environment variables, import other files (with the right module setup), or branch on the operating system.
// ~/.fxrc.js
// Pick a built-in theme
module.exports.theme = 'monokai'
// Other built-in themes: oneDark, oneLight, nord, dracula, gruvbox
// Custom helper functions become available inside filters
module.exports.helpers = {
// Use as: keys(x) to list top-level keys
keys: obj => Object.keys(obj),
values: obj => Object.values(obj),
len: x => (Array.isArray(x) ? x.length : Object.keys(x).length),
// Domain-specific helper
email: u => u.email,
adult: u => u.age >= 18,
// Format a Unix timestamp as ISO
iso: ts => new Date(ts * 1000).toISOString(),
}
// Override syntax-highlighting colors per token type
module.exports.colors = {
key: '#79c0ff',
string: '#a5d6ff',
number: '#ffa657',
boolean: '#ff7b72',
null: '#8b949e',
}With the helpers above in place, filters become shorter and more readable:
cat data.json | fx 'x => x.users.filter(adult).map(email)'
cat data.json | fx 'x => len(x.events)'
cat data.json | fx 'x => x.logs.map(l => ({...l, time: iso(l.timestamp)}))'For teams, check ~/.fxrc.js into a dotfiles repo so everyone gets the same helpers and theme. For a one-off override on a single invocation, fx accepts a --no-rcflag that skips the config file — useful in CI where you want the default behavior regardless of what is on the runner's home directory.
fx vs jq vs jless vs gron — when to use each
Four tools dominate terminal JSON work and they solve overlapping but distinct problems. The right pick depends on the task, not on which tool is best in the abstract.
| Tool | Strength | Filter language | Mode | Best for |
|---|---|---|---|---|
fx | Interactive exploration + JS filters | JavaScript | Interactive TUI or pipe | Exploring unfamiliar JSON, then writing a filter |
jq | Non-interactive transformation | jq DSL | Pipe only | Production scripts, CI, repeatable transforms |
jless | Fast read-only viewing | None (no filter) | Interactive TUI | Browsing large JSON dumps with vim-style keys |
gron | Flatten for grep / diff | None (grep handles filtering) | Pipe only | Diffing two JSON files, grep-friendly paths |
Concrete picks by task:
- You have a 50 MB API response and want to find the one field you care about. Use
fx— fold the tree, search, copy the path withp. - You need to extract
.users[].emailacross 10,000 nightly runs in CI. Usejq— it is faster to start, has no JS engine warmup, and the DSL is stable. - You want a fast vim-style pager for a JSON file you only need to read. Use
jless— Rust-fast, no JS engine, simple keybindings. - You need to diff two JSON files and see exactly which paths changed. Use
gron file1.json > a.txt; gron file2.json > b.txt; diff a.txt b.txt. - You are inside a shell session and just want pretty colors.
jq .works; so doesfx | headwithout a filter argument.
For an in-browser equivalent of fx's interactive view, our Online JSON viewer guide covers the browser-side tools that complement these CLIs.
Integrating with curl, kubectl, and CI pipelines
fx is built to compose with the rest of the Unix toolbox. Anything that emits JSON on stdout becomes a candidate input — the most common upstream tools are curl for HTTP, kubectl for Kubernetes resources, and aws/gcloud CLIs for cloud APIs.
# curl: explore an API response interactively
curl -sH 'Authorization: Bearer $TOKEN' https://api.example.com/orders | fx
# curl: pull one field non-interactively for a script
ORDER_ID=$(curl -s https://api.example.com/orders/latest | fx '.id')
# kubectl: drill into a pod spec
kubectl get pod my-pod -o json | fx '.spec.containers[0].env'
# kubectl: find all pods with a specific image
kubectl get pods -A -o json | fx 'x => x.items
.filter(p => p.spec.containers.some(c => c.image.includes("nginx")))
.map(p => p.metadata.name)'
# AWS CLI: filter EC2 instances by tag
aws ec2 describe-instances --output json | fx 'x => x.Reservations
.flatMap(r => r.Instances)
.filter(i => i.Tags?.some(t => t.Key === "env" && t.Value === "prod"))
.map(i => i.InstanceId)'
# CI step: assert a value from a JSON test report
cat report.json | fx 'x => x.failures.length === 0' | grep -q true || exit 1In CI, prefer the non-interactive form with an explicit filter argument. fx detects the absence of a TTY and skips the TUI automatically, but passing the filter explicitly makes the script easier to read and avoids any surprise if a future fx release changes the auto-detection. For Bash-heavy workflows that already use jq elsewhere, our Parse JSON in Bash guide covers the patterns that compose well with both tools.
One gotcha: when you embed a JavaScript filter inside a Bash command, quoting matters. Wrap the entire expression in single quotes and avoid single quotes inside the JS itself — use double quotes (or template literals) for string literals. If you need both, switch to a heredoc or write the filter to a file and pass it with --filter=path/to/filter.js.
Key terms
- fx
- An interactive terminal JSON viewer that doubles as a non-interactive pipe filter. Uses JavaScript as its filter language. Written in Go as of the 35.x line (rewritten from Node.js).
- TUI (Terminal User Interface)
- A full-screen interactive program that runs inside a terminal — like
vim,less, orhtop. fx's TUI is what opens when stdout is detected as a TTY. - ~/.fxrc.js
- fx's configuration file. Plain JavaScript that exports a theme, custom helper functions, and color overrides. Loaded on every invocation unless
--no-rcis passed. - jless
- A separate Rust-written tool that provides a read-only JSON pager with vim-style keybindings. No filtering or transformation — pure viewing. See jless.io.
- gron
- A tool that flattens JSON into greppable assignment statements (e.g.,
json.users[0].name = "alice"). Designed to make JSON diffable and searchable with line-oriented Unix tools. - jq DSL
- A small functional language for filtering and transforming JSON, built into the
jqbinary. Distinct from JavaScript — has its own pipe operator, selectors, and reductions.
Frequently asked questions
What is fx for JSON?
fx is an interactive terminal JSON viewer that lets you explore, search, and filter JSON documents the same way you would explore a filesystem in a file manager. You pipe JSON into it (cat data.json | fx), and fx opens a full-screen TUI where arrow keys navigate the tree, Enter expands and collapses nodes, slash starts a search, and dot opens a filter line that accepts JavaScript expressions. Originally written in Node.js, fx was rewritten in Go for the 35.x line and now ships as a single static binary — no Node runtime required. It also works as a non-interactive pipe when stdout is not a TTY, so the same tool serves both exploration and scripting. fx is open source and maintained at github.com/antonmedv/fx, and as of 2026 the project is in active development with monthly releases.
How is fx different from jq?
jq is a non-interactive command-line filter built around a small functional DSL — you write expressions like .users[] | select(.age > 30) | .name and pipe data through. fx is interactive first: you launch it, navigate the document with arrow keys, fold and unfold subtrees, and only write a filter when you already know what you are looking for. The filter language differs too — fx uses JavaScript (x => x.users.filter(u => u.age > 30)), which is familiar to most web developers, while jq has its own syntax that you learn once and use everywhere. For one-off shell pipelines and CI scripts, jq is usually the right pick. For exploring an unfamiliar API response or a large log file, fx is faster because you can see the shape before you commit to a query. Many engineers keep both installed.
Can fx run in CI as a non-interactive tool?
Yes. fx detects whether stdout is a terminal and switches modes automatically. When you run cat data.json | fx in a shell, the interactive TUI opens. When you run cat data.json | fx '.users[0].name' in a CI pipeline (or pipe the result onward, as in cat data.json | fx '.x' | wc -l), fx skips the TUI, evaluates the expression you passed as an argument against the input, and writes the result to stdout. This dual-mode behavior is why fx fits in shell pipelines alongside jq, grep, and awk. For CI specifically, install fx in your runner image (npm install -g fx, brew install fx, or download the static Go binary from GitHub Releases) and call it like any other text-processing tool. The non-interactive form also takes JavaScript expressions, so the same filter you developed interactively works unchanged in your pipeline.
How do I install fx?
fx ships through every major package manager. On macOS or Linux with Homebrew, brew install fx pulls the latest Go binary. With npm, npm install -g fx works on any platform that has Node available (the npm package wraps the same Go binary). On Windows, scoop install fx is the simplest path; chocolatey works too with choco install fx. Snap users run sudo snap install fx. For everything else, grab the static binary directly from the GitHub Releases page — fx is a single executable with no runtime dependencies, so you can drop it into /usr/local/bin or a Docker image without worrying about Node or Go versions. Verify the install with fx --version (you should see 35.x as of 2026). The legacy Node.js fx is still on npm under @medv/fx for projects that need the older API.
Does fx support JavaScript filters?
Yes — JavaScript is fx's native filter language, which is one of its main differentiators from jq. In the interactive TUI, press . to open the filter line and type any JS expression. The input document is bound to a variable (x), so x.users.filter(u => u.age > 30) and (x => x.users.length)(x) both work. Arrow functions, template literals, optional chaining, destructuring, and standard methods like map, filter, reduce, sort, and Object.entries are all available. fx exposes a small standard library on top of JavaScript: helper functions like keys, values, and len for terser code, plus the ability to define your own helpers in ~/.fxrc.js. Filters that return undefined or throw show as no result, which makes iterative refinement painless — type, watch the panel update, adjust.
What is jless and how does it differ from fx?
jless is a read-only JSON pager modeled after less — it opens a full-screen view of a JSON document, lets you navigate with vim-style keybindings (j, k, /, n), fold and unfold nodes, and copy paths or values to the clipboard. It does not filter, transform, or run expressions — it is purely a viewer. fx does everything jless does and adds JavaScript-based filtering, search, theming, and a non-interactive pipe mode. Why pick jless then? Speed and minimalism — jless is written in Rust, opens enormous files quickly, and has a smaller learning curve because its scope is narrower. Many users keep both: jless for fast read-only browsing of large JSON dumps, fx when they need to filter or compute. See https://jless.io/ for the project. gron is in the same family but solves a different problem — it flattens JSON into grep-friendly assignment lines.
Can fx handle streaming JSON?
fx reads its full input into memory before opening the interactive viewer, so it is not a true streaming tool in the way jq --stream or jstream are — for multi-gigabyte JSONL files where you only want to scan, those are better picks. That said, fx loads quickly even on large documents (tens of MB) thanks to the Go rewrite, and the TUI renders lazily, so navigating a deeply nested object stays responsive. For newline-delimited JSON (JSONL), pipe through fx with each line treated as one document if you wrap with something like jq -s before fx, or use fx's --slurp behavior. For genuine streaming pipelines — tailing a log of JSON events, processing each record as it arrives — combine jq --stream with grep or use a purpose-built tool like jstream. fx is the wrong tool when you cannot afford to buffer the input.
Is fx faster or slower than jq?
For non-interactive one-shot filters on small to medium files, jq is usually faster — it is a battle-tested C program with a tight interpreter loop, and its startup time is in the single-digit milliseconds. fx (since the Go rewrite) is competitive: cold start is around 10-30 ms depending on platform, and filter evaluation is fast because it runs on a Go-embedded JavaScript engine. The practical difference is rarely noticeable in a shell pipeline. Where fx wins clearly is interactive exploration — there is no jq equivalent for browsing a 50 MB API response by folding and unfolding nodes. Where jq wins clearly is heavy non-interactive transformation: aggregating thousands of records, building complex pipelines, or running inside performance-sensitive CI. Pick the tool that matches the workflow, not the one with the better microbenchmark.
Further reading and primary sources
- fx on GitHub — Official repository, releases, and changelog for the Go-based fx 35.x line
- jless — Read-only JSON pager — The closest companion tool — vim-style viewing without filtering
- jq Manual — Reference for the jq DSL — selectors, reductions, and pipe operator
- gron — Make JSON greppable — Flatten JSON to assignment lines for use with grep, diff, and awk
- JavaScript MDN — Array methods — Reference for the filter, map, reduce, and Object.groupBy methods used inside fx filters