The CLI guesses what you want based on how it was invoked. That’s the right default for humans and a sharp edge for scripts. This step teaches you to never get cut.
The auto-detect rules
When you run render services in your terminal, you get a pretty paginated picker. When the same command runs in CI, you get tabular text. That’s not magic - it’s a single rule:
flowchart TD
start["render <command>"]
flag{"--output flag set?"}
envvar{"RENDER_OUTPUT env set?"}
tty{"stdout is a TTY?"}
i["Output: interactive picker"]
t["Output: text (tab-separated)"]
flag -->|"yes"| useflag["Use that format"]
flag -->|"no"| envvar
envvar -->|"yes"| useenv["Use that format"]
envvar -->|"no"| tty
tty -->|"yes"| i
tty -->|"no"| t
start --> flag
In English: a flag beats an env var beats auto-detect. If nothing is set, the CLI checks whether stdout is a TTY and picks interactive (TTY) or text (pipe/CI).
That’s why piping accidentally “breaks” interactive mode - once stdout isn’t a TTY, the picker bails out.
The four output formats
| Format | Best for | Caveats |
|---|---|---|
interactive | Humans, browsing | Requires a TTY; won’t work in pipes or CI |
json | Scripts, jq, anything programmatic | The format you’ll use most as a power user |
yaml | Eyeballing complex objects | Slower to parse, fine for one-off inspection |
text | Quick grep, awk-style pipelines | Field positions can shift between releases - don’t over-trust it |
Pick json for anything you’d want to script against. It’s stable, parseable, and jq makes the rest easy.
Set the format three ways
Per command (-o / --output)
render services -o jsonHighest precedence. Use it when you’re mixing interactive and scripted use in the same session.
Per shell session (RENDER_OUTPUT)
export RENDER_OUTPUT=jsonrender servicesrender deploys list srv-xxxSet it once, every subsequent command emits JSON. Great for scripting sessions where you don’t want to repeat -o json on every line.
Per config file (the output field)
output: interactiveLowest precedence; the default you fall back to when nothing else is set. Most people leave this on interactive.
--confirm and why CI needs it
Several mutating commands prompt you to confirm before running. Examples: render deploys create, render psql connecting to production. The prompt is a safety net interactively, and a hang in CI.
render deploys create srv-xxx --wait --confirm -o jsonAnything that mutates state, when scripted, gets --confirm. Read-only commands (services, logs, deploys list) don’t need it.
See the difference: interactive vs scripted
The same command, two ways:
$render services[picker UI: arrow keys to navigate, enter to select, q to quit] Name Type Plan URL > api web starter https://api.acme.onrender.com worker worker starter cache kv starter app-db psql basic-1gb
The picker filters as you type, lets you scroll, and - if you press Enter - takes you deeper into a service’s deploys, logs, env vars, and so on.
$render services -o json --confirm | jq '.[] | {name, id, type}'{ "name": "api", "id": "srv-xxxxxxxxxxxxxxxx", "type": "web_service" } { "name": "worker", "id": "srv-yyyyyyyyyyyyyyyy", "type": "background_worker" }
The same data, but as JSON your script can iterate, filter, and pipe.
A test you can run right now
Run this twice and look at the difference:
render services # interactive pickerrender services | head # text format (because stdout is a pipe)render services -o json | jq '.[0]' # JSON (explicit flag)RENDER_OUTPUT=json render services # JSON (env var)If render services -o json | jq '.[0]' doesn’t print a JSON object, the most common cause is jq parsing the text format - make sure -o json is on the render call, not on jq.
Practical scripting template
Whenever you’re writing a script that calls render, start from this template:
#!/usr/bin/env bashset -euo pipefailexport RENDER_API_KEY="${RENDER_API_KEY:?must be set}"export RENDER_OUTPUT=jsonCONFIRM=( --confirm )
services=$(render services)echo "$services" | jq -r '.[] | "\(.name)\t\(.id)"'| Line | Why |
|---|---|
set -euo pipefail | Bash fails on error, undefined vars, and broken pipes - the right default for any operations script |
${RENDER_API_KEY:?must be set} | Fails fast if the key isn’t set, with a clear message |
export RENDER_OUTPUT=json | All subsequent render calls emit JSON - no need to repeat -o json |
CONFIRM=( --confirm ) | Lets you splat "${CONFIRM[@]}" into any mutating command, keeping it explicit and greppable |
You’ll see this template again in step 10.
Common gotchas
| Symptom | Root cause | Fix |
|---|---|---|
| CI job hangs forever | Missing --confirm on a mutating command | Add --confirm |
| Script gets text output, not JSON | --output flag missing, env var missing | Set RENDER_OUTPUT=json at the top |
jq: error (at <stdin>:1): Cannot iterate over null | render printed an empty list as text, then jq choked | Confirm -o json is set, and check whether the resource actually exists |
| Pretty-printed output in CI logs | Some commands default to text, which still includes ANSI color codes | Add NO_COLOR=1 to your CI env, or use -o json |
What you learned
- Flag > env var > config file > TTY auto-detect - four levels of precedence, two of which you control per-command
- `-o json` is the right default for anything scripted; `text` is for quick `grep` and `jq` is the right friend for `json`
- `--confirm` is the magic word that prevents CI hangs on mutating commands
- The scripting template (`set -euo pipefail`, `RENDER_OUTPUT=json`, explicit `--confirm` array) gets reused throughout this course