Human-readable logs are fine when one person runs a conversion locally. CI needs something more structured. If a build job converts or checks images, the next script may need to count files, identify failures, summarize savings, or flag unsupported inputs. That is where NDJSON reports are useful.
NDJSON means newline-delimited JSON. Each line is a complete JSON object. This format works well for long-running command-line jobs because a parser can read events one by one instead of waiting for a huge final document.
The quality risk is treating NDJSON as a buzzword. A CI guide that invents fields like event, input, and output leaves readers with a parser that does not match the tool. The article has to name the real event types, explain that the stream is not a JSON array, and show what the build should do when conversion is truncated, partially failed, or only mechanically successful.
Why Plain Logs Are Not Enough#
A conversion log like this may be readable:
converted hero.jpg to hero.webp
failed product-13.png unsupported input
But it is awkward for automation. A script has to parse human text, and small wording changes can break the parser.
GetWebP's structured output is more stable because each line is an envelope with a type discriminator and command-specific data:
{"@timestamp":"2026-04-12T10:00:00.000Z","@level":"info","@message":"GetWebP CLI 1.3.0","@module":"getwebp.cli","type":"version","data":{"getwebp":"1.3.0","ui":"1"}}
{"@timestamp":"2026-04-12T10:00:01.234Z","@level":"info","@message":"Converted 1/1 files","@module":"getwebp.convert","type":"convert.completed","data":{"processed":1,"total":1,"successCount":1,"failedCount":0,"results":[{"file":"hero.jpg","outputPath":"/abs/hero.webp","originalSize":204800,"newSize":133120,"savedRatio":0.35,"saved":"35.0%","quality":82,"qualityMode":"auto","status":"success"}]}}
Now CI can count failures, group errors, or post a summary to a pull request without guessing.
Use JSON Output for Machine Readers#
GetWebP CLI supports --json, which emits structured NDJSON for automation and agent workflows:
getwebp ./public/images --json
In CI, redirect that output to a report file:
set +e
getwebp ./public/images \
-o ./dist/images \
--recursive \
--quality 82 \
--json > image-report.ndjson 2> image-report.stderr.log
code=$?
set -e
That report can be uploaded as a workflow artifact, parsed by a later step, or stored with benchmark results. Keep stderr as a separate log file. Human warnings are useful for debugging, but they are not the machine contract.
Know the Events That Matter#
The JSON output reference defines the GetWebP envelope and conversion events. A CI parser usually needs these:
| Event type | Meaning | CI use |
|---|---|---|
version | CLI version preamble; first line of every --json invocation | Record tool version |
convert.completed | Conversion ran to completion for the attempted file set | Read successCount, failedCount, and results[] |
convert.truncated | Free plan limit stopped the run before every file was processed | Fail or split the job; do not call the run complete |
convert.failed | Command could not start, such as bad flags or missing input | Fail fast and print data.code / data.hint |
The stream as a whole is not a JSON array. Each line is a separate JSON object. Tools like jq can process that stream directly, but a custom parser should split on newlines and parse one object per line.
Treat the Schema as a Contract#
Once another script reads the report, field names become a contract. Do not invent local aliases like input, bytesIn, or event unless your script clearly maps them from the real fields.
For team workflows, document the fields your parser depends on:
Required fields:
type
data.processed
data.successCount
data.failedCount
data.results[].file
data.results[].outputPath
data.results[].originalSize
data.results[].newSize
data.results[].savedRatio
data.results[].quality
data.results[].qualityMode
data.results[].status
data.results[].error
The converter may emit more fields, but the parser should clearly state which ones are required. This keeps the report useful without making every future CLI improvement a breaking change.
Fail CI on Clear Mechanical Problems#
NDJSON reports are useful for mechanical checks:
- unsupported files
- missing outputs
- conversion failures
- license or rate-limit errors
- unexpected empty output
- command crashes
These are appropriate CI failures because the job can identify them objectively.
Visual quality is different. CI can report that a WebP file was created, but it cannot decide whether a model's face, product texture, or UI screenshot remains acceptable. Keep human visual review in the pull request process.
Build a Small Parser#
A parser can be simple. For example, a Node.js script can read each line, parse JSON, and collect failed events:
import fs from "node:fs";
const raw = fs.readFileSync("image-report.ndjson", "utf8").trim();
const events = raw ? raw.split(/\r?\n/).map((line) => JSON.parse(line)) : [];
const failedStartup = events.find((event) => event.type === "convert.failed");
if (failedStartup) {
console.error(`Image conversion could not start: ${failedStartup.data.code}`);
if (failedStartup.data.hint) console.error(failedStartup.data.hint);
process.exit(1);
}
const truncated = events.find((event) => event.type === "convert.truncated");
if (truncated) {
console.error(
`Image conversion truncated: processed ${truncated.data.processed}, skipped ${truncated.data.skipped}`
);
process.exit(1);
}
const completed = events.find((event) => event.type === "convert.completed");
if (!completed) {
console.error("No convert.completed event found");
process.exit(1);
}
const failures = completed.data.results.filter((result) => result.status === "error");
if (failures.length > 0) {
console.error(`Image conversion failed for ${failures.length} file(s):`);
for (const failure of failures) {
console.error(` ${failure.file}: ${failure.error}`);
}
process.exit(1);
}
const savedBytes = completed.data.results
.filter((result) => result.status === "success")
.reduce((total, result) => total + (result.originalSize - result.newSize), 0);
console.log(`Converted ${completed.data.successCount} image(s), saved ${savedBytes} bytes`);
Keep the first parser narrow. Add more checks only after the basic failure path is reliable.
Use jq for Focused Checks#
For CI jobs that only need a few facts, jq can parse the NDJSON stream directly.
Fail if any file-level conversion failed:
jq -e '
select(.type == "convert.completed")
| .data.failedCount == 0
' image-report.ndjson >/dev/null
Print failed inputs:
jq -r '
select(.type == "convert.completed")
| .data.results[]
| select(.status == "error")
| "\(.file): \(.error)"
' image-report.ndjson >&2
Export successful outputs as CSV:
jq -r '
select(.type == "convert.completed")
| .data.results[]
| select(.status == "success")
| [.file, .outputPath, .originalSize, .newSize, .saved, .quality, .qualityMode]
| @csv
' image-report.ndjson > image-summary.csv
Detect truncation:
if jq -e 'select(.type == "convert.truncated")' image-report.ndjson >/dev/null; then
echo "Image conversion did not process every file." >&2
exit 1
fi
These checks are intentionally mechanical. They should answer "what happened in the conversion job," not "is this image beautiful enough to ship."
Make Reports Reviewable#
Upload the NDJSON file as a CI artifact or convert it into a short Markdown summary. Reviewers usually do not want to read hundreds of JSON lines, but they may need the raw report when a job fails.
Useful summary fields include:
- CLI version
- total files attempted
- successful conversions
- total failures
- skipped files
- largest outputs
- largest negative or weak savings
- files skipped
- output paths
- quality and quality mode
GitHub's Actions documentation explains artifacts and workflow steps. Google's WebP documentation provides format background for the outputs being reported.
Keep Exit Codes Beside the Report#
The NDJSON file gives detail; the process exit code gives the category of failure. Capture both. A convert.completed event with failedCount > 0 is not the same situation as convert.truncated, and neither is the same as a usage error that emitted convert.failed.
A practical CI step should store:
image-report.ndjson
image-report.stderr.log
getwebp exit code
short Markdown summary
That evidence lets a reviewer tell whether the problem was a corrupt input, a Free-plan limit, a bad command flag, an auth problem, or a visual-quality concern that automation cannot decide.
Use Reports to Improve the Workflow#
Over time, NDJSON reports can reveal patterns. Maybe screenshots fail more often than photos. Maybe one folder contains unsupported files. Maybe the output size is not improving because images were already optimized.
That evidence helps teams adjust the pipeline without guessing. NDJSON does not make image optimization high quality by itself, but it makes the mechanical part observable, parseable, reviewable, and easier to improve over time.

Jack
GetWebP EditorJack writes GetWebP guides about local-first image conversion, WebP workflows, browser compatibility, and practical performance checks for teams that publish images on the web.