jq: process JSON in the terminal with ten essential expressions
TL;DR
jq reads, filters and transforms JSON in the terminal and pairs naturally with curl. Learn ., .[], select, map, group_by and @csv; use -r for shell variables, --arg for inputs, and never overwrite the source file in place.
APIs return JSON, config files are JSON, logs are JSON. Every time they need one field or a few filtered items, many people open a browser, paste into an online tool, or write a quick Python script. jq does it right in the terminal: a compact query language for reading, filtering and transforming JSON that pairs naturally with curl and other commands. About ten expressions cover most daily needs.

Installation
brew install jq # macOS
sudo apt install jq # Ubuntu/Debian
# Or via Docker, per the README:
docker run --rm -i ghcr.io/jqlang/jq:latest < package.json '.version'
The ten most-used expressions
Examples use users.json, an array of objects {"id", "name", "age", "team"}:
| Expression | Meaning |
|---|---|
. | Pretty-print the JSON with colours |
.[0].name | The name field of the first element |
.[] | .name | Iterate the array, take each name |
map(.id) | New array containing only ids |
.[] | select(.age > 30) | Filter by a condition |
{name, team} | Build a new object with just those fields |
length | Count elements |
group_by(.team) | map({team: .[0].team, n: length}) | Group and count by team |
sort_by(.age) | reverse | Sort descending |
.[] | [.id, .name] | @csv | Export CSV |
Real-world examples
# Names and star counts of an organisation's GitHub repos
curl -s "https://api.github.com/orgs/astral-sh/repos?per_page=100" \
| jq -r '.[] | "\(.stargazers_count)\t\(.name)"' | sort -rn | head
# Read the version from package.json in a script
VERSION=$(jq -r .version package.json)
# Pass shell variables into jq safely
jq --arg env "prod" '.[] | select(.env == $env)' deployments.json
# Change a field and write to a new file
jq '.scripts.test = "vitest run"' package.json > tmp.json && mv tmp.json package.json
# Filter JSON logs by level
cat app.log | jq -c 'select(.level == "error") | {time, msg}'
Important flags
-r: print strings without quotes, needed when assigning to shell variables.-c: one result per line, compact for further piping.-s: slurp multiple JSON inputs into one array (useful for JSON-lines logs).-n: don't read input; build new JSON.-e: exit non-zero if the result is null/false, handy in script conditions.
Caveats
- Never do
jq ... file > file: the shell truncates the file before jq reads it. Write to a temp file and rename. - Use
--arginstead of splicing shell strings into jq expressions to avoid quoting bugs and injection. - jq loads whole documents into memory; for very large JSON consider
--stream.
How to apply it
- Dev: Use jq to explore API responses while debugging, and in build scripts to read versions and config.
- Tech Lead: Standardise on JSON logs so the whole team can query them with jq during incidents, without heavy tooling.
- PM: A single jq line can answer a quick data question from an API (how many records, grouped by status) without waiting for a dashboard.
Sources & references
- jqlang/jq — jqlang (GitHub)