An API hands you 4,000 lines of JSON and you need three fields from it. Scrolling works once. The second time, reach for jq.
This jq cookbook collects the filters you’ll use most, grouped by task. Every example runs against the same small sample file, and every output shown came from running the command with jq 1.8.2. Copy a recipe, swap in your own field names, and move on.
What You’ll Learn
- When to reach for jq, and when another tool fits better.
- How jq filters and pipes work, so you can read any recipe.
- How to pull values, filter arrays with
select, and handle missing keys. - How to reshape objects, rename fields, and update values.
- How to count, sum, group, and sort.
- How to turn JSON into CSV, TSV, or plain text for other tools.
- How to pass shell variables into jq and use jq in scripts.
- How to apply all of it to real output from the GitHub API, kubectl, and the AWS CLI.
When to Use jq
jq earns its place when JSON stands between you and an answer. These use cases cover the jobs it handles best:
- Read an API response. Pretty-print the payload, then pull the two or three fields you need out of a few thousand lines.
- Filter a long array. Keep the records matching a condition and drop the rest, straight from the command line.
- Reshape output for another tool. Rename fields, remove nulls, and hand a smaller object to the next command in the pipe.
- Turn JSON into CSV or TSV. Move an API result into a spreadsheet, a database import, or anything that speaks columns.
- Pull one value inside a shell script. Assign a token, an ID, or a count to a variable with
-r, and retire the fragilegrepandcutchain. - Query kubectl, the AWS CLI, and the GitHub API. All three emit JSON, so one set of filters works across them.
- Build JSON to send. Construct a request body from shell variables with
--argand let jq handle the quoting.
Other tools fit better in a few places. Use grep when the text happens to be JSON but the question is a plain string match. Use a general-purpose language when the logic outgrows a one-liner, or when you need to join several documents. Reach for yq with YAML and dasel when the format varies.
Set Up jq and the Sample Data
Install jq with your package manager.
# macOS
brew install jq
# Debian or Ubuntu
sudo apt-get install jq
# Windows
winget install jqlang.jqCheck the version. The recipes below use features available in jq 1.7 and later.
jq --versionCreate the sample file. Every recipe in this cookbook reads employees.json.
cat > employees.json <<'EOF'
{
"company": "Acme",
"employees": [
{ "id": 1, "name": "Ada Lopez", "email": "ada@acme.example", "dept": "Engineering", "age": 34, "salary": 145000, "skills": ["go", "sql"], "manager": null },
{ "id": 2, "name": "Ben Okafor", "email": "ben@acme.example", "dept": "Engineering", "age": 28, "salary": 118000, "skills": ["python"], "manager": 1 },
{ "id": 3, "name": "Chen Wu", "email": "chen@contractor.example", "dept": "Design", "age": 41, "salary": 99000, "skills": ["figma", "css"], "manager": 1 },
{ "id": 4, "name": "Dana Smith", "email": "dana@acme.example", "dept": "Sales", "age": 25, "salary": 72000, "skills": [], "manager": 3 }
]
}
EOFPrefer to try the recipes without installing anything? The runner below holds the same employees.json and executes real jq 1.8.2 in your browser. Edit either box and press Run. Every filter in this cookbook works in it.
Press Run to execute this filter.
How jq Filters Work
A jq program is a filter. It takes JSON in, produces JSON out, and the pipe | passes one filter’s output to the next, the same way the shell pipes text between commands.
one object per employee"] B --> C["select(.age > 30)
keep matching objects"] C --> D[".name
take one field"] D --> E["Ada Lopez
Chen Wu"]
Four pieces of syntax cover most of what you’ll read:
.is the current input..companyreads a field from it..[]iterates an array, emitting each element separately.|pipes each result into the next filter.-rprints strings raw, without JSON quotes, and-cprints each result on one line.
jq Cookbook: Read Values
Pretty-Print JSON
Pass . to print the whole document, indented and colorized. This is the fastest way to make an unreadable API response readable.
jq . employees.json
curl --silent https://api.github.com/repos/jqlang/jq | jq .Get a Field
Quote-free output matters when the result feeds another command, so add -r for strings.
jq '.company' employees.json
jq -r '.company' employees.json"Acme"
AcmeGet a Value From Every Array Element
.employees[] emits each employee, and .name takes the name from each one.
jq -r '.employees[].name' employees.jsonAda Lopez
Ben Okafor
Chen Wu
Dana SmithIndex, Slice, and Count an Array
Arrays start at 0. Negative indexes count from the end, and slices include the start index but not the end index.
jq -c '.employees[0]' employees.json
jq -r '.employees[-1].name' employees.json
jq -c '.employees[1:3] | map(.name)' employees.json
jq '.employees | length' employees.json{"id":1,"name":"Ada Lopez","email":"ada@acme.example","dept":"Engineering","age":34,"salary":145000,"skills":["go","sql"],"manager":null}
Dana Smith
["Ben Okafor","Chen Wu"]
4Handle Missing Keys With a Default
jq returns null for a key that doesn’t exist instead of failing. The alternative operator // replaces null or false with a default.
jq '.employees[0].address.city' employees.json
jq '.employees[0].address.city // "unknown"' employees.jsonnull
"unknown"Parse JSON From a URL
Pipe curl into jq. The --silent flag keeps curl’s progress meter out of the output.
curl --silent https://raw.githubusercontent.com/jeffabailey/learn/main/tools/jq/offices.json \
| jq -r '.[] | "\(.name) is in \(.location)"'Office 1 is in Oregon
Office 2 is in California
Office 3 is in AustriaFilter JSON With select
select(condition) passes its input through when the condition is true and drops it otherwise. Put it after .[] to filter an array element by element.
Filter by a Field Value
jq -c '.employees[] | select(.age > 30) | {name, age}' employees.json
jq -r '.employees[] | select(.dept == "Engineering") | .name' employees.json{"name":"Ada Lopez","age":34}
{"name":"Chen Wu","age":41}
Ada Lopez
Ben OkaforCombine Conditions With and, or, and not
jq -r '.employees[] | select(.dept == "Engineering" and .salary > 120000) | .name' employees.json
jq -r '.employees[] | select(.email | endswith("@acme.example") | not) | .email' employees.jsonAda Lopez
chen@contractor.exampleMatch Strings With a Regular Expression
test takes a regular expression and optional flags. The "i" flag ignores case. Escape a literal dot as \\. inside the jq string.
jq -r '.employees[] | select(.name | test("^[ab]"; "i")) | .name' employees.json
jq -r '.employees[] | select(.email | test("@acme\\.example$")) | .name' employees.jsonAda Lopez
Ben Okafor
Ada Lopez
Ben Okafor
Dana SmithFilter on Array Contents
index("sql") returns the position of "sql" in the array, or null when it’s absent, so select keeps only employees who list the skill. Compare length to find empty arrays.
jq -r '.employees[] | select(.skills | index("sql")) | .name' employees.json
jq -r '.employees[] | select(.skills | length == 0) | .name' employees.jsonAda Lopez
Dana SmithFind Null Values
jq -r '.employees[] | select(.manager == null) | .name' employees.jsonAda LopezKeep a Filtered Array Instead of a Stream
.[] | select(...) emits separate results. Wrap the filter in map to get one array back, which you can count or pass on.
jq '.employees | map(select(.dept == "Engineering")) | length' employees.json2Reshape JSON Objects
Pick Fields
{id, name} is shorthand for {id: .id, name: .name}.
jq -c '.employees | map({id, name})' employees.json[{"id":1,"name":"Ada Lopez"},{"id":2,"name":"Ben Okafor"},{"id":3,"name":"Chen Wu"},{"id":4,"name":"Dana Smith"}]Rename Fields
jq -c '.employees | map({employee_id: .id, full_name: .name})' employees.json[{"employee_id":1,"full_name":"Ada Lopez"},{"employee_id":2,"full_name":"Ben Okafor"},{"employee_id":3,"full_name":"Chen Wu"},{"employee_id":4,"full_name":"Dana Smith"}]Add and Delete Fields
+ merges objects, with the right side winning on conflicts. del removes one or more paths.
jq -c '.employees[0] | . + {remote: true}' employees.json
jq -c '.employees[0] | del(.skills, .manager)' employees.json{"id":1,"name":"Ada Lopez","email":"ada@acme.example","dept":"Engineering","age":34,"salary":145000,"skills":["go","sql"],"manager":null,"remote":true}
{"id":1,"name":"Ada Lopez","email":"ada@acme.example","dept":"Engineering","age":34,"salary":145000}Update a Value in Place
The update operator |= runs a filter on a value and writes the result back. This recipe gives everyone a 5% raise.
jq -c '.employees | map(.salary |= . * 1.05 | {name, salary})' employees.json
jq -c '.employees[] | .skills |= map(ascii_upcase) | {name, skills}' employees.json[{"name":"Ada Lopez","salary":152250},{"name":"Ben Okafor","salary":123900},{"name":"Chen Wu","salary":103950},{"name":"Dana Smith","salary":75600}]
{"name":"Ada Lopez","skills":["GO","SQL"]}
{"name":"Ben Okafor","skills":["PYTHON"]}
{"name":"Chen Wu","skills":["FIGMA","CSS"]}
{"name":"Dana Smith","skills":[]}Remove Null Fields
with_entries turns an object into {key, value} pairs, runs your filter on each pair, and rebuilds the object.
jq -c '.employees[0] | with_entries(select(.value != null))' employees.json{"id":1,"name":"Ada Lopez","email":"ada@acme.example","dept":"Engineering","age":34,"salary":145000,"skills":["go","sql"]}List the Keys of an Object
jq -c '.employees[0] | keys' employees.json["age","dept","email","id","manager","name","salary","skills"]Turn an Array Into a Lookup Object
Parentheses around a key evaluate it, so {(.name): .dept} uses each name as a key. add merges the resulting objects.
jq -c '.employees | map({(.name): .dept}) | add' employees.json{"Ada Lopez":"Engineering","Ben Okafor":"Engineering","Chen Wu":"Design","Dana Smith":"Sales"}Count, Sum, Group, and Sort
Sum and Average
jq '.employees | map(.salary) | add' employees.json
jq '.employees | map(.salary) | add / length' employees.json434000
108500Group and Count
group_by sorts by the key and returns an array of groups. Each group’s first element supplies the label.
jq -c '.employees | group_by(.dept) | map({dept: .[0].dept, count: length})' employees.json[{"dept":"Design","count":1},{"dept":"Engineering","count":2},{"dept":"Sales","count":1}]Sort and Find the Largest Value
sort_by sorts ascending. Add reverse for descending order, or use max_by and min_by when you only need one element.
jq -c '.employees | sort_by(.age) | map(.name)' employees.json
jq -c '.employees | sort_by(.age) | reverse | map(.name)' employees.json
jq -r '.employees | max_by(.age) | .name' employees.json["Dana Smith","Ben Okafor","Ada Lopez","Chen Wu"]
["Chen Wu","Ada Lopez","Ben Okafor","Dana Smith"]
Chen WuFlatten and Deduplicate
[.employees[].skills[]] collects every skill into one array, and unique sorts it and drops duplicates.
jq -c '[.employees[].skills[]] | unique' employees.json["css","figma","go","python","sql"]Convert JSON to CSV, TSV, and Text
Convert JSON to CSV
Build an array per row, then format it with @csv. Use -r or the quotes get escaped a second time.
jq -r '.employees[] | [.id, .name, .dept] | @csv' employees.json1,"Ada Lopez","Engineering"
2,"Ben Okafor","Engineering"
3,"Chen Wu","Design"
4,"Dana Smith","Sales"Add a header row by emitting it first.
jq -r '["id","name","dept"], (.employees[] | [.id, .name, .dept]) | @csv' employees.jsonConvert JSON to TSV
@tsv is easier to feed into cut, awk, or a while read loop because fields never contain unescaped tabs.
jq -r '.employees[] | [.name, .email] | @tsv' employees.jsonAda Lopez ada@acme.example
Ben Okafor ben@acme.example
Chen Wu chen@contractor.example
Dana Smith dana@acme.exampleBuild Strings With Interpolation
\(...) inside a string inserts the result of a filter.
jq -r '.employees[] | "\(.name) works in \(.dept)"' employees.jsonAda Lopez works in Engineering
Ben Okafor works in Engineering
Chen Wu works in Design
Dana Smith works in SalesUse jq in Shell Scripts
Pass Shell Variables Into jq
Never paste a shell variable into the filter string. Quoting breaks the first time a value contains a quote. Pass strings with --arg and numbers or JSON with --argjson.
jq -r --arg dept Sales '.employees[] | select(.dept == $dept) | .name' employees.json
jq -r --argjson min 100000 '.employees[] | select(.salary >= $min) | .name' employees.jsonDana Smith
Ada Lopez
Ben OkaforBuild JSON From Scratch
-n starts with no input, which makes jq a safe JSON generator for request bodies.
jq -n --arg name "Eve Park" --argjson age 30 '{name: $name, age: $age}'{
"name": "Eve Park",
"age": 30
}Branch on a Result With the Exit Status
-e sets the exit status from the last output: 1 when it’s false or null, 0 otherwise. Use it in if statements instead of comparing strings.
if jq -e '.employees | any(.dept == "Legal")' employees.json > /dev/null; then
echo "Legal has staff"
else
echo "No one in Legal"
fiNo one in LegalEdit a JSON File in Place
jq has no in-place flag. Write to a temporary file, then move it over the original, so a failed filter can’t truncate your file.
tmp="$(mktemp)" && jq '.company = "Acme Corp"' employees.json > "$tmp" && mv "$tmp" employees.jsonSearch and Combine JSON
Find a Key Anywhere in a Document
.. walks every value recursively. .email? reads the key where it exists, and // empty drops the misses.
jq -r '[.. | .email? // empty] | join(", ")' employees.jsonada@acme.example, ben@acme.example, chen@contractor.example, dana@acme.exampleList the Paths to Matching Values
paths(f) returns the path to every value where f is true, which helps you learn the shape of an unfamiliar document.
jq -c '[paths(type == "number")] | .[:3]' employees.json[["employees",0,"id"],["employees",0,"age"],["employees",0,"salary"]]Combine Several Files
-s (slurp) reads every input into one array. * deep-merges two objects.
jq -s 'map(.employees | length) | add' employees.json employees.json
jq -c '.employees[0] * {skills: ["go", "sql", "rust"]} | {name, skills}' employees.json8
{"name":"Ada Lopez","skills":["go","sql","rust"]}Real-World jq Recipes
The same filters work on JSON from the tools you already run. The kubectl and AWS CLI outputs below come from sample responses in the same shape those commands return.
Query the GitHub API
curl --silent https://api.github.com/repos/jqlang/jq \
| jq '{name: .full_name, stars: .stargazers_count}'
curl --silent "https://api.github.com/repos/jqlang/jq/releases?per_page=3" \
| jq -r '.[] | "\(.tag_name)\t\(.published_at[:10])"'jq-1.8.2 2026-06-20
jq-1.8.1 2025-07-01
jq-1.8.0 2025-06-01List Kubernetes Pods That Aren’t Running
kubectl get pods -o json \
| jq -r '.items[] | select(.status.phase != "Running") | "\(.metadata.name)\t\(.status.phase)"'worker-5c2a Pending
cron-1b3e FailedList EC2 Instances With Their Name Tags
AWS returns tags as a list of Key and Value pairs, so select the pair whose key is Name.
aws ec2 describe-instances \
| jq -r '.Reservations[].Instances[] | [.InstanceId, .State.Name, (.Tags[]? | select(.Key == "Name") | .Value)] | @tsv'i-0abc running api
i-0def stopped batchjq Cheat Sheet
jq . # pretty-print
jq -r '.key' # raw string, no quotes
jq -c '.' # one result per line
jq '.a.b.c' # nested field
jq '.a // "default"' # default for null or missing
jq '.[]' # iterate an array
jq '.[0]' / '.[-1]' / '.[2:5]' # index, last element, slice
jq 'length' # array, object, or string length
jq '.[] | select(.n > 1)' # filter elements
jq 'map(select(.ok))' # filter, keep an array
jq 'map({id, name})' # pick fields
jq '. + {k: 1}' # add or overwrite a field
jq 'del(.k)' # delete a field
jq '.n |= . + 1' # update in place
jq 'with_entries(select(.value))' # filter object entries
jq 'map(.n) | add' # sum
jq 'group_by(.k)' # group
jq 'sort_by(.k) | reverse' # sort descending
jq 'unique' # sort and deduplicate
jq -r '.[] | [.a, .b] | @csv' # CSV
jq -r '.[] | [.a, .b] | @tsv' # TSV
jq -r '"\(.a) and \(.b)"' # string interpolation
jq --arg v "$VAR" '.k == $v' # pass a string
jq --argjson n 5 '.k > $n' # pass a number or JSON
jq -n '{k: 1}' # build JSON from nothing
jq -e '.ok' # exit status from the result
jq -s '.' # slurp inputs into one array
jq '.. | .k? // empty' # find a key anywhereLearn jq: Beyond the Basics
When a recipe here doesn’t fit, these resources go deeper.
- jq Manual: the full reference for every built-in function, operator, and flag.
- jq Tutorial: the official walkthrough using live GitHub API data.
- jq Wiki Cookbook: community recipes for harder problems like recursive updates and streaming.
- jq Playground: paste JSON and a filter in the browser and see the output as you type.
- Online Generate Test Data: generate larger JSON files to practice on.
Related Content
- What Is Nushell? covers a shell that treats JSON as structured data, an alternative to piping text through jq.
- Learn Amazon EC2 uses jq to pull a private key out of an AWS CLI response.
- How to Use sqlite-utils loads JSON into SQLite when a query outgrows jq.
- How to Use jq With kubectl applies these filters to a Kubernetes cluster.
- jq Cheat Sheet lists the same filters in lookup form, without the explanations.

Comments #