# The jq command

> Learn the jq command to filter and transform JSON from curl on the CLI. Covers fields, select, map, and raw output with a real API example.

Author: [Flavio Copes](https://flaviocopes.com/about/) | Published: 2026-09-01 | Topics: [CLI](https://flaviocopes.com/tags/cli/) | Canonical: https://flaviocopes.com/jq-command/

`jq` reads, filters, and transforms JSON on the command line. It is the tool I reach for when `curl` returns a large response and I need three fields from it.

You can also validate files, build new JSON objects, update arrays, and make shell scripts fail when data is missing.

## Install jq

On macOS with Homebrew:

```bash
brew install jq
```

On Ubuntu or Debian:

```bash
sudo apt install jq
```

Check the installation:

```bash
jq --version
```

## Create a small JSON file

We will use this `releases.json` file for the first examples:

```json
{
  "project": "notes-api",
  "owner": {
    "name": "Flavio",
    "team": "web"
  },
  "releases": [
    {
      "version": "1.0.0",
      "status": "published",
      "downloads": 240
    },
    {
      "version": "1.1.0",
      "status": "draft",
      "downloads": 0
    },
    {
      "version": "2.0.0",
      "status": "published",
      "downloads": 510
    }
  ]
}
```

Pretty-print and validate it:

```bash
jq '.' releases.json
```

The `.` filter means “return the input.” jq parses the file, then prints formatted JSON.

Invalid JSON produces an error and a non-zero exit code. This makes `jq '.'` a useful validation command.

## Think in filters

A jq program is a **filter**. It receives an input value and produces zero, one, or several output values.

Filters compose with `|`. The output from the left becomes the input on the right:

```bash
jq '.owner | .name' releases.json
```

This is equivalent to:

```bash
jq '.owner.name' releases.json
```

The result is still valid JSON:

```json
"Flavio"
```

The shell pipe and jq pipe look similar, but they operate at different levels:

```bash
curl -s https://api.github.com/repos/biomejs/biome \
  | jq '.stargazers_count'
```

The shell sends bytes from `curl` to jq. Inside jq, filters pass parsed JSON values to other filters.

If command-line pipes are new to you, the free [Shell Commands course](https://flaviocopes.com/courses/terminal/) explains standard input and output first. The free [curl course](https://flaviocopes.com/courses/curl/) covers the HTTP side.

## Read object fields

Select a top-level field:

```bash
jq '.project' releases.json
```

Read a nested field:

```bash
jq '.owner.team' releases.json
```

Keys with spaces or punctuation need bracket syntax:

```bash
jq '.owner["display-name"]' releases.json
```

A missing field normally returns `null`:

```bash
jq '.owner.email' releases.json
```

Use the optional operator when a value may have the wrong shape:

```bash
jq '.owner.email?' releases.json
```

The `?` suppresses the type error. It does not turn a missing value into an empty string.

## Read arrays

Array indexes start at zero:

```bash
jq '.releases[0]' releases.json
```

Get the last element with a negative index:

```bash
jq '.releases[-1]' releases.json
```

Return a slice:

```bash
jq '.releases[0:2]' releases.json
```

The `[]` iterator emits every element as a separate result:

```bash
jq '.releases[]' releases.json
```

Read one field from every release:

```bash
jq '.releases[] | .version' releases.json
```

This produces three JSON strings, one per line.

Collect those results into one array with square brackets:

```bash
jq '[.releases[] | .version]' releases.json
```

Now jq produces one JSON array.

## Use map to transform an array

`map(filter)` applies a filter to every array element and collects the results:

```bash
jq '.releases | map(.version)' releases.json
```

This is equivalent to the previous array construction.

Build a new shape for each item:

```bash
jq '.releases | map({version, downloads})' releases.json
```

The shorthand `{version, downloads}` reads those fields from the current object.

Rename fields explicitly:

```bash
jq '.releases | map({tag: .version, count: .downloads})' releases.json
```

Object construction is one of jq's most useful features. We can reduce a large API response to the exact contract another command needs.

## Filter with select

`select(condition)` keeps values where the condition is true.

Return published releases:

```bash
jq '.releases[] | select(.status == "published")' releases.json
```

Return only their versions:

```bash
jq '[
  .releases[]
  | select(.status == "published")
  | .version
]' releases.json
```

jq ignores whitespace in the filter, so a long program can span several lines.

Combine conditions with `and` and `or`:

```bash
jq '[
  .releases[]
  | select(
      .status == "published" and .downloads >= 500
    )
]' releases.json
```

Use parentheses when a condition mixes operators. The next person should not have to remember precedence rules.

## Sort and aggregate data

Sort releases by download count:

```bash
jq '.releases | sort_by(.downloads)' releases.json
```

Reverse the order:

```bash
jq '.releases | sort_by(.downloads) | reverse' releases.json
```

Add the downloads:

```bash
jq '.releases | map(.downloads) | add' releases.json
```

Count array items with `length`:

```bash
jq '.releases | length' releases.json
```

Find the release with the largest count:

```bash
jq '.releases | max_by(.downloads)' releases.json
```

These filters are clearer than extracting text and sending it through several line-oriented commands.

## Provide defaults for missing values

The alternative operator `//` supplies a fallback for `false` or `null`:

```bash
jq '.owner.email // "not configured"' releases.json
```

Be careful with Boolean data. `false // true` returns `true`, because jq treats `false` as needing the alternative too.

Use `has()` when you need to distinguish a missing key from a key explicitly set to `null`:

```bash
jq '.owner | has("email")' releases.json
```

## Produce raw text

By default, jq prints strings as JSON strings with quotes:

```bash
jq '.project' releases.json
```

Output:

```text
"notes-api"
```

Use `-r` for raw text:

```bash
jq -r '.project' releases.json
```

Output:

```text
notes-api
```

This is useful when another shell command expects text:

```bash
jq -r '.releases[].version' releases.json
```

Use compact output for one JSON value per line:

```bash
jq -c '.releases[]' releases.json
```

Compact JSON is still JSON. Raw output is not necessarily JSON.

## Pass shell values safely

Do not inject a shell variable directly into a jq program. Pass it with `--arg`:

```bash
release_status=published

jq --arg status "$release_status" '
  .releases[] | select(.status == $status)
' releases.json
```

`--arg` always creates a string.

Pass a JSON number, Boolean, array, or object with `--argjson`:

```bash
minimum=200

jq --argjson minimum "$minimum" '
  .releases[] | select(.downloads >= $minimum)
' releases.json
```

This keeps shell quoting separate from jq syntax and avoids broken filters when a value contains spaces or punctuation.

## Update JSON

Change one field with assignment:

```bash
jq '.owner.team = "platform"' releases.json
```

This changes jq's output. It does not edit the input file.

Update values using their current value with `|=`:

```bash
jq '.releases[].downloads |= . + 10' releases.json
```

To replace a file, write a new file first and move it only after jq succeeds:

```bash
jq '.owner.team = "platform"' releases.json > releases.new.json && \
  mv releases.new.json releases.json
```

Do not redirect output to the same input path. The shell truncates the file before jq can read it.

## Read a stream of JSON values

jq normally reads every whitespace-separated JSON value from standard input and runs the filter once for each value.

For a JSON Lines file such as:

```text
{"level":"info","message":"server started"}
{"level":"error","message":"database unavailable"}
```

select errors directly:

```bash
jq 'select(.level == "error")' app.jsonl
```

Use `--slurp` or `-s` to collect the input values into one array:

```bash
jq -s 'map(select(.level == "error"))' app.jsonl
```

Slurping loads all values into memory. For a large stream, process one value at a time instead.

jq also has a `--stream` mode for very large nested JSON documents. It emits paths and leaf values rather than the original object shape. That is powerful, but the filters are less readable. Use it when normal parsing genuinely exceeds memory.

## Make shell scripts fail on missing data

By default, a filter that returns `false` or `null` can still leave jq with a successful status.

Use `-e` to connect the last output value to the exit status:

```bash
jq -e '.releases | length > 0' releases.json
```

The command exits with `0` when the final result is neither `false` nor `null`. It exits with `1` for `false` or `null`, and with another non-zero status for parse or program errors.

This works well in scripts:

```bash
if jq -e '.status == "ready"' deployment.json > /dev/null; then
  echo 'Deployment is ready'
else
  echo 'Deployment is not ready'
fi
```

`-e` checks the filter result, not just whether the input was valid JSON.

## Quote jq programs correctly

On Unix shells, wrap jq programs in single quotes:

```bash
jq '.owner.name' releases.json
```

Many jq characters also have meaning to the shell. Single quotes keep the shell from expanding `$variables`, wildcards, and other syntax before jq sees it.

Put large filters in a file:

```text
.releases
| map(select(.status == "published"))
| sort_by(.downloads)
| reverse
```

Save it as `published.jq`, then run:

```bash
jq -f published.jq releases.json
```

This is easier to review and version than a dense one-line filter.

## Common jq mistakes

Watch for these:

- forgetting that `.items[]` emits several results
- using `-r` when the next command expects JSON
- interpolating shell variables instead of using `--arg`
- redirecting output to the input file
- treating a missing key and `null` as the same requirement
- slurping a file too large for memory
- parsing JSON with `grep` or `sed` before trying jq
- forgetting `-e` when a filter result should control a script

## How I use jq

I use jq to inspect API responses, trim deployment metadata, and validate JSON in shell scripts. I usually start with `.`, add one field or iterator, then shape the final object.

I keep complicated filters in a `.jq` file. Once a filter needs several variables and branches, I also ask whether a small JavaScript program would be clearer.

jq is at its best between tools: parse real JSON, select the exact data, and send clean JSON or text to the next command. Read [JSON](https://flaviocopes.com/json/) if you want to review the data format itself.
