c8 Coverage JSON Output Flag: A Practical Guide
If you've ever tried to feed Node.js test coverage into a CI pipeline, a badge service, or a pull-request bot, you've hit the same wall: the pretty terminal table is useless to a machine. You need JSON. The good news is that c8 can produce machine-readable coverage with a single flag.
This guide shows you the exact c8 coverage JSON output flag, when to reach for json versus json-summary, and how to wire the result into automated checks. By the end you'll know which reporter emits coverage-summary.json, where it lands on disk, and how to read the lines-covered percentage programmatically — no guesswork, no scraping stdout with fragile regex.
What Is the c8 Coverage JSON Output Flag?
The flag is --reporter=json-summary for a compact machine-readable summary, or --reporter=json for full per-file data. c8 passes the value to Istanbul's reporters, writing coverage-summary.json or coverage-final.json into the coverage directory.
There is no dedicated boolean --json switch. Instead, c8 reuses Istanbul's reporter set and lets you pick a format through the --reporter option (short form: -r). That single option is the lever for every output format c8 supports, JSON included.
Because --reporter accepts an array, you can emit several formats in one run — say, a human-readable table plus a JSON summary for tooling. That flexibility is the reason there's no separate JSON flag: everything routes through the reporter list.
c8 JSON Reporters at a Glance
Not every JSON output is the same. Pick the reporter that matches what the consumer of the file actually needs.
| Reporter | Flag | Output file | Best for |
|---|---|---|---|
json-summary | --reporter=json-summary | coverage-summary.json | Totals + per-file percentages (badges, gates, PR bots) |
json | --reporter=json | coverage-final.json | Full statement/branch/line detail for deep tooling |
text-summary | --reporter=text-summary | stdout | A quick human glance in CI logs |
lcovonly | --reporter=lcovonly | lcov.info | Uploading to Codecov or Coveralls |
???? Suggested visual: a simple decision-tree infographic — "Need a percentage? → json-summary. Need every line? → json. Uploading to a service? → lcovonly."
For most automation, json-summary is the sweet spot. It's small, easy to parse, and includes the aggregate percentages you'll want for a coverage gate.
How to Output JSON Coverage with c8
Here's the shortest path from zero to a JSON coverage file:
- Install c8 as a dev dependency. Run
npm i -D c8in your project. - Run your tests through c8 with the JSON reporter. For a summary, use
c8 --reporter=json-summary npm test. - Combine reporters if you want both. Chain them:
c8 --reporter=text --reporter=json-summary npm testprints the table and writes the file. - Locate the output. By default it lands at
./coverage/coverage-summary.json. - Change the directory if needed. Add
-o(or--reports-dir), e.g.c8 -o reports/coverage --reporter=json-summary npm test.
That's the whole workflow. The reporter runs after your tests finish, converting V8's raw coverage into the Istanbul JSON shape.
Understanding coverage-summary.json
The json-summary reporter produces a file with a top-level total object plus one entry per source file. The total block is what most scripts read:
{ "total": { "lines": { "total": 161, "covered": 161, "skipped": 0, "pct": 100 }, "statements": { "total": 166, "covered": 166, "skipped": 0, "pct": 100 }, "functions": { "total": 49, "covered": 49, "skipped": 0, "pct": 100 }, "branches": { "total": 24, "covered": 18, "skipped": 0, "pct": 75 } } }Each metric carries total, covered, skipped, and pct. The pct field is the percentage you're usually after. To grab the lines-covered percentage in a script, jq makes it a one-liner:
jq '.total.lines.pct' coverage/coverage-summary.json???? Suggested visual: an annotated "anatomy of coverage-summary.json" diagram highlighting the
total.lines.pctpath.
Because the structure is stable and documented, you can rely on it in build scripts without brittle parsing. That predictability is exactly why it beats scraping terminal text.
Configuring JSON Output in package.json or .c8rc
Passing flags on every command gets tedious. c8 reads configuration from a c8 section in package.json, or from a dedicated file such as .c8rc.json or .nycrc.json. One important rule: in config files you drop the -- prefix and use the long-form option name.
A .c8rc.json example:
{ "reporter": ["text", "json-summary"], "reports-dir": "./coverage", "all": true }The equivalent inside package.json:
{ "c8": { "reporter": ["text", "json-summary"], "reports-dir": "./coverage" } }With this in place, c8 npm test produces both the terminal table and the JSON summary automatically. Commit the config and every teammate — and every CI run — gets identical output.
The NODE_V8_COVERAGE Environment Variable
Under the hood, c8 sets the NODE_V8_COVERAGE environment variable to a temporary directory. Node.js then dumps raw V8 coverage JSON there while your tests run, and c8 converts that raw data into Istanbul reports afterward.
You can use this variable directly. Set NODE_V8_COVERAGE=./coverage/tmp before running a script, and Node writes raw coverage JSON files to that folder on exit. This is useful when a process isn't launched through the c8 binary — for example, a child process or a custom runner.
Keep one distinction in mind: the raw V8 files are not the tidy coverage-summary.json. They're low-level, byte-offset coverage data. To turn them into a summary, point c8 at them and run a report (covered next). For most projects, letting c8 manage NODE_V8_COVERAGE for you is simpler than setting it by hand.
Generating JSON Without Re-running Tests
Already ran your suite and only now realized you need JSON? You don't have to run everything again. As long as the underlying coverage data still exists, the report subcommand regenerates output in any format:
c8 report --reporter=json-summaryThis reads the stored coverage data and writes a fresh coverage-summary.json. It's handy for adding a new format to an existing run, or for producing several formats from a single expensive test execution.
Using JSON Coverage in CI/CD
JSON output shines in automation. A few common patterns:
- Enforce a coverage gate. Fail the build when coverage drops:
c8 check-coverage --lines 95 --functions 95 --branches 95. You can also addcheck-coverage: trueand thresholds to your config file. - Log and export. Combine
text-summary(readable in CI logs) withjson-summary(parsed by downstream steps):c8 -r text-summary -r json-summary npm test. - Post PR comments. Read
total.lines.pctfrom the summary and have a bot comment the number — or the delta againstmain. - Generate a badge. Feed the percentage into a shields.io endpoint or a static badge generator.
???? Suggested embedded content: a short GitHub Actions YAML snippet showing a step that runs c8 with
json-summaryand a follow-up step that reads the percentage.
The pattern is always the same: emit coverage-summary.json, then let another step read a single, reliable field.
Common Pitfalls to Avoid
A few things trip people up when they first wire up JSON coverage:
- Files that never load show 0% — or don't show at all. By default V8 only reports files that were executed. Add
--allso untested source files count against your totals instead of silently inflating them. - Reporter name typos. It's
json-summary, hyphenated — notjsonSummaryorjson_summary. A wrong name is silently ignored, leaving you with no file. - Wrong directory expectations. Output defaults to
./coverage. If a CI step can't find the file, check whether a configreports-diris redirecting it elsewhere. - Old Node.js. c8 depends on Node's native V8 coverage, so a sufficiently modern Node runtime is required. If coverage comes back empty, verify your Node version first.
Conclusion
Getting machine-readable coverage out of c8 comes down to one choice: the --reporter flag. Use --reporter=json-summary for a compact coverage-summary.json with the percentages CI needs, or --reporter=json for the full coverage-final.json when you want every detail. Combine reporters to keep human-readable logs alongside the JSON, move the output with -o, and lock your preferences into .c8rc.json or package.json so every run is consistent.
Your next step: add c8 --reporter=text --reporter=json-summary to your test script, commit a config file, and wire check-coverage into your pipeline. Once the JSON is flowing, badges, gates, and PR comments are just a jq query away.
Further reading: the c8 README on GitHub, Istanbul's alternative reporters documentation, and the Node.js CLI documentation for NODE_V8_COVERAGE.

- Coding Agent Help - Wiki
- GStreamer + TypeScript: GObject Breaking Changes
- Firebase Auth Token Expiry: How Long ID Tokens Last
- i18next Supported Locale Codes: There Is No Fixed List
- BoringSSL's Latest Stable Release Version, Explained
- c8 Coverage JSON Output Flag: A Practical Guide
- OpenSSL Latest Stable Release: The 2026 Version Guide
- Polars DataFrame Filter: Always-True Predicate
- Latest Stable Version of HashiCorp Terraform (2026)
- GStreamer 1.0: The Major Version That Changed AudioFormat
- GitHub GraphQL API isTotal Field: It Doesn't Exist
