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:
$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.
render services -o json \ | jq -r '.[] | select(.service.name == "api") |.service.id'Output: srv-xxxxxxxxxxxxxxxx.
Wrap it in a shell function:
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.
SRV=$(render-id api)render deploys list "$SRV"Recipe 2: list everything, compactly
render services -o json \ | jq -r '.[] | "\(.service.id)\t\(.service.type)\t\(.service.name)"' \ | column -tOutput:
srv-xxxxxxxxxxxxxxxx web_service apisrv-yyyyyyyyyyyyyyyy background_worker workersrv-zzzzzzzzzzzzzzzz key_value cachedpg-aaaaaaaaaaaaaaaa postgres app-dbcolumn -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”:
render services -o json \ | jq -r '.[] | select(.service.type == "web_service") |.service.name'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:
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:
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:
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 }'}render-describe api{ "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:
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" doneOutput:
srv-xxxxxxxxxxxxxxxx live a1b2c3d 2024-04-01T14:30:00Zsrv-yyyyyyyyyyyyyyyy live e4f5g6h 2024-04-01T13:55:00ZIt’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
| Pattern | What it does |
|---|---|
select(.field == "value") | Filter; keeps elements that match |
.[] | Flatten the array; “for each element…” |
\(.field) inside strings | Interpolation - build text rows from JSON |
--arg name value | Pass a shell var safely into the jq filter |
? after a path | Suppress 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.
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