Render Tutorials
Render CLI for power users

List, filter, and pipe with jq

⏱ 9 min

render services is the entry point to every other power-user workflow. Once you can ask it precise questions and pipe the answers into other commands, the rest of the CLI feels like one big shell function.

This step is a jq cookbook with render services on one side and useful answers on the other.

The shape of render services -o json

Run it once and look at the shape:

Terminal
$render services -o json | jq '.[0]'
{ "service": { "id": "srv-xxxxxxxxxxxxxxxx", "name": "api", "type": "web_service", "suspended": "not_suspended", "slug": "api", "branch": "main", "repo": "https://github.com/acme/api", "autoDeploy": "yes", "createdAt": "2024-03-12T10:00:00Z", "updatedAt": "2024-04-01T14:30:00Z", "serviceDetails": { "plan": "starter", "region": "oregon", "url": "https://api.acme.onrender.com", "env": "node" } } }

Note the wrapper: every entry is a wrapper object with a nested service field, not the service object directly. That’s the most common source of jq confusion - you need .service (or .[].service for the whole array).

Recipe 1: get a service ID by name

The single most useful one-liner. Every later step needs a service ID, and typing them out is error-prone.

Terminal
render services -o json \
| jq -r '.[] | select(.service.name == "api") |.service.id'

Output: srv-xxxxxxxxxxxxxxxx.

Wrap it in a shell function:

~/.zshrc or ~/.bashrc
render-id() {
local name="$1"
render services -o json \
| jq -r --arg n "$name" '.[] | select(.service.name == $n) |.service.id'
}

Now render-id api returns the ID. The --arg form is safer than string interpolation - it handles names with quotes or backslashes correctly.

Terminal
SRV=$(render-id api)
render deploys list "$SRV"

Recipe 2: list everything, compactly

Terminal
render services -o json \
| jq -r '.[] | "\(.service.id)\t\(.service.type)\t\(.service.name)"' \
| column -t

Output:

Output
srv-xxxxxxxxxxxxxxxx web_service api
srv-yyyyyyyyyyyyyyyy background_worker worker
srv-zzzzzzzzzzzzzzzz key_value cache
dpg-aaaaaaaaaaaaaaaa postgres app-db

column -t aligns the columns - it’s available on macOS and most Linux distros by default. If it isn’t, drop it and live with tabs.

Recipe 3: filter by type

select lets you ask “all my web services” or “every Postgres database”:

Terminal
render services -o json \
| jq -r '.[] | select(.service.type == "web_service") |.service.name'
Terminal
render services -o json \
| jq -r '.[] | select(.service.type == "postgres") |.service.id'

The known type strings are stable across CLI releases: web_service, background_worker, private_service, static_site, cron_job, key_value, postgres.

Recipe 4: find suspended services

A common drift check - services that should be running but aren’t:

Terminal
render services -o json \
| jq -r '.[] | select(.service.suspended == "suspended") |.service.name'

In a real workspace you want zero output. If something appears here that you didn’t intentionally suspend, you’ve found a problem worth investigating.

Recipe 5: which services autodeploy from main

Useful before a release - make sure nothing got accidentally pointed at a feature branch:

Terminal
render services -o json \
| jq -r '.[]
| select(.service.type == "web_service")
| "\(.service.name)\t\(.service.branch)\t\(.service.autoDeploy)"'

Anything pointing at a branch other than main (or whatever your default is) gets one row, and you can investigate.

Recipe 6: build a “describe” function

Borrow this if you constantly find yourself looking up a service’s region, plan, and URL:

~/.zshrc or ~/.bashrc
render-describe() {
local name="$1"
render services -o json \
| jq -r --arg n "$name" '
.[]
| select(.service.name == $n)
| {
id:.service.id,
type:.service.type,
plan:.service.serviceDetails.plan,
region:.service.serviceDetails.region,
url:.service.serviceDetails.url,
branch:.service.branch,
autoDeploy:.service.autoDeploy,
suspended:.service.suspended
}'
}
Terminal
render-describe api
Output
{
"id": "srv-xxxxxxxxxxxxxxxx",
"type": "web_service",
"plan": "starter",
"region": "oregon",
"url": "https://api.acme.onrender.com",
"branch": "main",
"autoDeploy": "yes",
"suspended": "not_suspended"
}

Three commands of muscle memory: render-id, render-describe, and render services for browsing. Most of the rest of the CLI is “do something with a service ID.”

Recipe 7: latest deploy status per service

This one is a teaser for step 06, but it shows how listings compose:

Terminal
render services -o json \
| jq -r '.[] | select(.service.type == "web_service") |.service.id' \
| while read -r srv; do
latest=$(render deploys list "$srv" -o json --confirm \
| jq -r '.[0].deploy | "\(.status)\t\(.commit.id[0:7])\t\(.createdAt)"')
printf '%s\t%s\n' "$srv" "$latest"
done

Output:

Output
srv-xxxxxxxxxxxxxxxx live a1b2c3d 2024-04-01T14:30:00Z
srv-yyyyyyyyyyyyyyyy live e4f5g6h 2024-04-01T13:55:00Z

It’s not fast (one API call per service), but it’s a snapshot of “what’s running where” that you don’t get from any single CLI command.

Two jq patterns worth memorizing

PatternWhat it does
select(.field == "value")Filter; keeps elements that match
.[]Flatten the array; “for each element…”
\(.field) inside stringsInterpolation - build text rows from JSON
--arg name valuePass a shell var safely into the jq filter
? after a pathSuppress errors on missing fields (e.g. .service.serviceDetails.url?)

90% of your jq usage with render is just these. The rest you can Google.

When -o json | jq is overkill

For one-off questions where you don’t need to script, render services (interactive picker) is genuinely faster. The CLI’s filter-as-you-type is built specifically so you don’t have to write jq for trivia like “what’s the ID of the worker again?”. Save the scripting for the things you’ll do twice.

You want to count how many web services in your workspace are autodeploying from `main`. Which one-liner is closest to right?

What you learned

  • Every service is wrapped in `{ "service": {... } }` - the most common `jq` mistake is forgetting that wrapper
  • `render-id <name>` is the killer helper: build it once and every later step uses it
  • `select(.service.type == "...")` filters across all the type strings: `web_service`, `background_worker`, `private_service`, `static_site`, `cron_job`, `key_value`, `postgres`
  • For ad-hoc browsing the interactive picker is genuinely faster; save `jq` for things you'll do twice