Output formats
Render records as a table, JSON, CSV, RDF or your own template, and script against the exit codes.
Every command that emits records renders through the same formatter.
Pick a format with --format (or -f), or let goodread choose: a table when writing to a terminal, JSONL when piped.
Formats
goodread editions 2792775 -f table # aligned columns for reading
goodread editions 2792775 -f jsonl # one JSON object per line, for piping
goodread editions 2792775 -f json # a single JSON array
goodread editions 2792775 -f csv # spreadsheet friendly
goodread editions 2792775 -f tsv # tab-separated
goodread editions 2792775 -f url # just the Goodreads URL of each row
goodread editions 2792775 -f raw # the underlying bytes, unformatted
| Format | Best for |
|---|---|
table |
Reading on a terminal |
jsonl |
Piping into another tool, one object at a time |
json |
Loading a whole result as an array |
csv / tsv |
Spreadsheets and quick column math |
url |
Feeding URLs into other commands |
raw |
The unformatted bytes |
--json is shorthand for -f json.
RDF is not in that list because it is not a per-record shape.
It comes out of the store rather than off a command: goodread export --to rdf.
What the JSON carries that the table does not
A table shows the fields worth reading by eye. The JSON also carries the provenance every record has:
surfacesandsources: which pages this record was built from.retrieved_atandbuild_id: when, and against which deployment of the site.via: which surface each field came from.level: which rung of the extraction ladder answered for each field,1for the Apollo cache,2forld+jsonandog:,3for a CSS selector.missed: plain sentences about what this page did not carry, like how many of a work's quotes the book page shows and which command reads the rest.
A field the read did not find is absent, not zero.
So checking for a key is meaningful, and a missing num_pages means nobody knows rather than zero pages.
Narrowing fields
goodread book 2767052 --fields title,isbn13,num_pages -f tsv
goodread editions 2792775 --fields isbn13,format,publisher -f csv
--fields names the JSON keys.
--no-header drops the header row in table, csv and tsv output, which is handy when a downstream tool expects bare rows.
Templating records
For full control over each line, apply a Go text/template. It walks the Go struct, not the JSON, so the field names are the Go names:
goodread book 2767052 --template '{{.Book.Title}} isbn {{.Book.ISBN13}}'
goodread book 2767052 --template '{{.Book.Title}} ({{.Book.NumPages}}pp)'
The two spellings are worth keeping straight: --fields isbn13 and --template '{{.Book.ISBN13}}' reach the same field by different routes.
Piping
Because the default adapts to the destination, the same command reads well by hand and parses cleanly in a pipe:
goodread editions 2792775 # a table, because this is a terminal
goodread editions 2792775 | jq -r .isbn13 # JSONL, because this is a pipe
--limit (or -n) caps the number of rows, and 0 means all.
Color
--color is auto by default: color on a terminal and none when piped.
Force it with --color always or turn it off with --color never.
Verbosity
-v says what is being read and what was not.
-vv adds every request and the extraction ladder.
Both go to stderr, so piping the output stays clean, and -q silences progress entirely.
Exit codes for scripting
goodread returns a stable exit code, and the numbering says what went wrong rather than how the run ended, which is what a script wants when it is deciding whether to retry.
| Code | Meaning |
|---|---|
0 |
success |
1 |
an error nothing else classified |
2 |
usage, including a config file that will not load |
3 |
network, meaning the site never answered |
4 |
the site answered and the answer was an error or a block |
5 |
extraction failed, or a record did not reconcile |
6 |
not found |
7 |
refused because robots.txt disallows the path |
8 |
robots.txt could not be read, so nothing can be checked against it |
These changed in v0.3.0. v0.2.0 used 3 for no data, 4 for partial and 5 for blocked.
Codes 7 and 8 are separate on purpose.
7 is a decision you can reverse by passing --no-robots.
8 is the tool refusing to guess because it could not read the rules at all, and no flag turns that into a proceed.
Code 1 stays a distinct code rather than being folded into the specific ones, because a run that exits 4 is telling a script something true about the site and a run that exits 1 is telling it we do not know.
goodread book 2767052 --json > book.json
case $? in
0) echo "got it" ;;
3|4) echo "the site, not us. retry later" ;;
7) echo "robots.txt says no" ;;
*) echo "failed" ;;
esac
See troubleshooting for what to do about each.