jq Tutorial: Step-by-Step Beginner's Guide (2026)
This jq tutorial takes you from zero to writing real filters in about thirty minutes. You will install jq, run your first command, filter arrays, select fields, and parse a live GitHub API response — every step has a command you can copy and the exact output it produces. By the end, you will know enough jq to replace most of the JSON-handling code you write in shell scripts.
Why learn jq
jq is a JSON processor for the command line. It does for JSON what awk does for columns and grep does for lines. If you ever pipe an API response through Python just to pull out one field, this jq tutorial replaces that habit with a single command.
Three places jq earns its keep daily:
- Parsing API responses — curl returns JSON, jq pulls out the field you want.
curl ... | jq -r '.access_token'is shorter than any Python equivalent. - Filtering structured logs — apps that emit JSON-per-line logs (kubectl, Docker, Cloud Run) are unreadable with grep. jq filters them by field.
- JSON-in-shell pipelines — gh, aws, kubectl, terraform output all support JSON. jq slices, joins, and reshapes that output without leaving the shell.
The official project lives at jqlang.github.io/jq. jq is written in C, ships as a single binary, and has no runtime dependencies.
1. Install jq
Pick the line for your OS. Each command installs the official jq build from your package manager:
# macOS
brew install jq
# Ubuntu, Debian
sudo apt-get install jq
# Fedora, RHEL, CentOS
sudo dnf install jq
# Arch
sudo pacman -S jq
# Windows (winget)
winget install jqlang.jq
# Windows (Chocolatey)
choco install jqConfirm the install:
$ jq --version
jq-1.7.1If you can't install jq right now — say, you're on a locked-down corporate laptop — skip ahead. The jq playground runs every example in this tutorial in your browser. Nothing gets uploaded.
2. Your first jq command
The smallest useful jq command is . — the identity filter. It reads JSON in, pretty-prints it out.
The . filter is the identity. Whatever JSON goes in, jq prints it back formatted and color-coded.
echo '{"name":"Jane","age":28}' | jq '.'{
"name": "Jane",
"age": 28
}Use .field to pull a single value out of an object. The dot prefix is required — it tells jq to walk into the object.
echo '{"name":"Jane","age":28}' | jq '.name'"Jane"By default, jq wraps strings in quotes. Add -r (raw output) when you want plain text — useful when piping into other commands.
echo '{"name":"Jane","age":28}' | jq -r '.name'JanePass a file path as the last argument. jq reads from stdin only if no file is given.
jq '.name' user.json"Jane"3. Filtering arrays
Most real-world JSON is an array of objects — a list of users, a list of pods, a list of orders. jq has dedicated syntax for arrays:
.[] takes an array and emits each element as a separate output. jq prints each on its own line.
echo '[{"name":"A"},{"name":"B"},{"name":"C"}]' | jq '.[]'{
"name": "A"
}
{
"name": "B"
}
{
"name": "C"
}Index by position with .[N]. Negative indices count from the end — .[-1] is the last element.
echo '[10,20,30,40]' | jq '.[0], .[-1]'10
40.[2:5] returns elements from index 2 up to but not including index 5 — same convention as Python.
echo '[10,20,30,40,50,60]' | jq '.[2:5]'[
30,
40,
50
]length returns the array length when applied to an array. Works on strings and objects too — count keys, count characters.
echo '[1,2,3,4,5]' | jq 'length'54. Selecting fields and nested keys
jq follows JSON's tree structure with dot notation. The same syntax works at any depth.
Chain dots to reach deep keys. Each dot moves one level down.
echo '{"user":{"profile":{"city":"Mumbai"}}}' | jq -r '.user.profile.city'MumbaiAppend ? to suppress errors when a key is missing. jq returns null instead of failing the pipeline.
echo '{"user":{}}' | jq '.user.profile?.city'nullObject construction with {key: .value} builds a new object from selected fields. Strips out anything you didn't ask for — useful for trimming sensitive data.
echo '{"id":42,"name":"Jane","email":"j@x.com","password":"hunter2"}' | jq '{id, name, email}'{
"id": 42,
"name": "Jane",
"email": "j@x.com"
}del(.key) removes a key in place. Combine with paths like del(.user.password) for nested deletes.
echo '{"name":"Jane","password":"hunter2"}' | jq 'del(.password)'{
"name": "Jane"
}5. Transformations: map, select, with_entries
These three filters do most of the work in real jq scripts. Learn them and you cover the long tail of day-to-day queries.
map runs the filter on each array element and collects the results into a new array. Equivalent to [.[] | f].
echo '[{"name":"A"},{"name":"B"}]' | jq 'map(.name)'[
"A",
"B"
]select acts as a filter. The pipeline keeps the input only if the condition is true. Combine with .[] to filter an array.
echo '[{"role":"admin"},{"role":"viewer"},{"role":"admin"}]' | jq '[.[] | select(.role == "admin")]'[
{
"role": "admin"
},
{
"role": "admin"
}
]with_entries converts the object to an array of {key, value} pairs, maps your filter over them, then rebuilds the object. Used to rename keys or transform values uniformly.
echo '{"first":"jane","last":"doe"}' | jq 'with_entries(.value |= ascii_upcase)'{
"first": "JANE",
"last": "DOE"
}sort_by(.field) sorts an array by a field. group_by(.field) buckets elements that share a value.
echo '[{"d":"e","n":"A"},{"d":"s","n":"B"},{"d":"e","n":"C"}]' | jq 'group_by(.d)'[
[
{
"d": "e",
"n": "A"
},
{
"d": "e",
"n": "C"
}
],
[
{
"d": "s",
"n": "B"
}
]
]6. Real-world examples
The examples above are warm-ups. Here is jq doing the work you will actually use it for.
GitHub returns JSON for every endpoint. This pulls the names and star counts of your most-starred repos. -r strips the surrounding quotes; @tsv tab-separates the fields for downstream tools like awk or column.
curl -s https://api.github.com/users/torvalds/repos \
| jq -r '.[] | [.name, .stargazers_count] | @tsv' \
| sort -k2 -rn | head -5linux 176432
libdc-for-dirk 298
subsurface 271
test-tlb 148
uemacs 720kubectl -o json emits the full pod spec for every pod in the namespace. jq narrows it to a table of pod name and current phase — useful in a status check script.
kubectl get pods -o json \
| jq -r '.items[] | [.metadata.name, .status.phase] | @tsv'api-7d4c8f9b6c-x4k2m Running
api-7d4c8f9b6c-z8n3p Running
worker-6f8b7d5c4f-q9w2r Pending
worker-6f8b7d5c4f-t3y5u CrashLoopBackOffAuth endpoints return JSON containing the bearer token. jq -r pulls the raw string so you can assign it to a shell variable and reuse it in the next curl call.
TOKEN=$(curl -s -X POST https://api.example.com/auth \
-d '{"user":"jane","pass":"x"}' \
| jq -r '.access_token')
echo "Got token: $TOKEN"Got token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...Combines select to filter the array, then length on the result. Drop-in replacement for a shell loop with counters.
kubectl get pods -o json \
| jq '[.items[] | select(.status.phase != "Running")] | length'2When you need to load JSON-per-line logs into a spreadsheet, jq's @csv handles quoting and escaping for you. The -r is required so the output isn't itself wrapped in quotes.
cat app.log \
| jq -r '[.timestamp, .level, .message] | @csv'"2026-05-23T10:14:02","INFO","login ok"
"2026-05-23T10:14:09","ERROR","db timeout"
"2026-05-23T10:14:11","INFO","login ok"7. Run jq in your browser
Every command in this jq tutorial runs in our jq playground. The playground compiles jq to WebAssembly so the filter executes inside your browser tab — your JSON never leaves your device. That matters when the JSON is a production API response, an auth payload, or anything you would rather not paste into someone else's server.
Workflow when you're learning:
- Copy a filter from this page.
- Paste your real JSON into the input pane.
- Edit the filter until the output looks right.
- Copy the working filter into your shell script.
Once the output is in the shape you want, you can paste it into our JSON formatter to validate, minify, or compare against another JSON tree.
Practical tips after the basics
Build filters one stage at a time
Start with '.', confirm the input looks right, then add one pipe at a time. Run after each addition. If the output suddenly goes empty, the most recent stage is the suspect.
Use type to debug pipe stages
When a filter returns null or fails, replace it with 'type' to see what jq thinks the input is. 'string' vs 'array' vs 'null' often explains the error in two seconds.
Save filters to a .jq file
Complex filters get hard to read in a single line. Save them to a file and run jq -f filter.jq input.json — easier to edit, easier to commit to git, easier to share with teammates.
Quote single-quotes carefully on Windows
PowerShell handles single-quote arguments differently from bash. On Windows, wrap the filter in double quotes and escape inner quotes, or run jq inside WSL where the bash syntax just works.
Pipe jq output into the next tool
jq plays well with awk, column, sort, uniq, and shell loops. The -r flag is critical here — without it, every value comes wrapped in quotes and the next tool sees them as part of the data.
Try every example in your browser
Open the jq playground in a new tab. Paste any command from this tutorial. See the output instantly — your JSON stays on your device.
Open jq Playground