Paste JSON, write a filter, and watch the output re-run as you type. Under the hood is genuine jq compiled to WebAssembly, so every flag, builtin, and edge case behaves exactly like the jq binary on your command line. When a filter works here, it works in your scripts.
Start filteringPaste JSON and write a filter to begin.
Reach for a jq filter often enough and the same handful of shapes keep recurring: iterate an array, group and count, pivot a list into a lookup, format a date. This is a working library of those filters organized by category, plus the CLI flag table and the common-failure list. Every snippet is copy-paste ready for use here or in your terminal.
Identity
.Passes input through unchanged. The trivial filter, useful for confirming what jq sees.
Field access
.name
.user.email
.["weird key with spaces"]Dot-notation walks an object. The square-bracket form is the escape hatch for keys that aren't valid identifiers.
Optional field access
.user.email?Returns null instead of erroring if .user is missing or not an object.
Array indexing
.[0]
.[-1]
.[2:5]Indexing, last element, and slicing. Slices are half-open like Python.
Array iteration
.[]Yields each element as a separate output. The most-used operator in real jq filters.
Pipe
.users | .[0] | .nameOutput of one filter becomes input to the next. Behaves like a shell pipe.
Map a transformation across each element
map(.price * 1.1)Equivalent to [.[] | (.price * 1.1)] but reads better.
Filter elements by predicate
map(select(.active == true))
map(select(.price >= 100))select(cond) keeps the input when cond is truthy and drops it otherwise.
Sort
sort
sort_by(.created_at)
sort_by(.priority) | reverseDescending sort is sort_by(...) | reverse; there is no sort_by_desc.
Group by
group_by(.category)Returns an array of arrays. Each inner array holds elements that share the same .category value.
Count occurrences (group and tally)
group_by(.status) | map({status: .[0].status, count: length})The canonical "group and count" idiom. Produces [{status: "open", count: 12}, ...].
Unique values
[.[] | .country] | unique
unique_by(.email)unique sorts then dedupes. unique_by lets you specify the key.
Flatten nested arrays
flatten
flatten(1)flatten collapses all nesting; flatten(n) collapses only n levels.
Min / max by key
min_by(.age)
max_by(.score)Length / count
length
[.[] | select(.active)] | lengthlength returns array length, object key count, or string length depending on input type.
Extract specific keys
{name, email}
{name: .name, role: .user_role}Shorthand {name} is sugar for {name: .name}.
Add or update a field
. + {processed_at: now}
.status = "shipped"+ merges objects (right wins on conflict). = assigns in place.
Delete a field
del(.password)
del(.users[].password)Convert object to entries (key/value pairs)
to_entriesTurns {a: 1, b: 2} into [{key: "a", value: 1}, {key: "b", value: 2}]. Required for any filter that iterates over keys.
Convert entries back to object
from_entriesInverse of to_entries.
Filter object by key or value
with_entries(select(.value > 10))
with_entries(select(.key | startswith("user_")))with_entries(f) is sugar for to_entries | map(f) | from_entries.
Get all keys
keys
keys_unsortedkeys returns sorted; keys_unsorted preserves insertion order.
Check for presence
has("email")
.users[0] | has("admin")Split a string
split(",")
split(" *, *"; "g")The second form uses a regex pattern.
Join an array of strings
join(", ")Case conversion
ascii_downcase
ascii_upcaseTrim whitespace
gsub("^\\s+|\\s+$"; "")jq has no built-in trim; this regex is the standard idiom.
Regex match
test("^[a-z]+$")
match("[0-9]{3}-[0-9]{4}")
capture("(?<area>[0-9]{3})-(?<line>[0-9]{4})")test returns a boolean, match returns positions, and capture returns named groups as an object.
Substring replacement
sub("foo"; "bar")
gsub("\\bfoo\\b"; "bar")sub replaces the first match; gsub replaces all.
String length
lengthOn a string, returns the number of Unicode codepoints.
Numeric formatting
. * 100 | floor
(. * 100 | round) / 100
tonumber
tostringjq has no printf; round to N decimals by multiplying, rounding, then dividing.
Pivot: turn a list into a lookup keyed by ID
[.[] | {(.id | tostring): .}] | add(.id | tostring) is needed because object keys must be strings.
Group, count, sort by count, take top N
group_by(.event)
| map({event: .[0].event, count: length})
| sort_by(-.count)
| .[:10]The top-10-by-frequency report. Sorting by -.count gives descending order without needing reverse.
Sum a field across all elements
[.[] | .amount] | add
map(.amount) | addFilter out null and empty values
map(select(.email != null and .email != ""))Conditional output
if .status == "error" then .message else empty endempty produces no output. Use it as the "drop this element" alternative inside if.
Walk a tree and transform every value
walk(if type == "string" then ascii_downcase else . end)walk recurses into every value in any nested structure.
Pretty-print a date field
.created_at | fromdate | strftime("%Y-%m-%d")fromdate parses ISO 8601 strings to epoch seconds; strftime formats them.
Diff two objects (keys in A, missing from B)
(.a | keys) - (.b | keys)Convert an array of objects to NDJSON
.[]With -c enabled, this prints each object as one compact line, which is the NDJSON format.
Convert objects to CSV rows
.[] | [.name, .email, .signup_date] | @csvCombined with -r, this produces lines suitable for spreadsheet import.
Each is a prefix applied to an array of values:
.users | map([.name, .email]) | .[] | @csvWith -r enabled, that prints "Ada","ada@example.com" on each line.
kubectl and Postman. Good for read-only field extraction; weak at transformation.-c flag produces it./users/0/email). Used by JSON Patch and OpenAPI.