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.

ReporterFlagOutput fileBest for
json-summary--reporter=json-summarycoverage-summary.jsonTotals + per-file percentages (badges, gates, PR bots)
json--reporter=jsoncoverage-final.jsonFull statement/branch/line detail for deep tooling
text-summary--reporter=text-summarystdoutA quick human glance in CI logs
lcovonly--reporter=lcovonlylcov.infoUploading 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:

  1. Install c8 as a dev dependency. Run npm i -D c8 in your project.
  2. Run your tests through c8 with the JSON reporter. For a summary, use c8 --reporter=json-summary npm test.
  3. Combine reporters if you want both. Chain them: c8 --reporter=text --reporter=json-summary npm test prints the table and writes the file.
  4. Locate the output. By default it lands at ./coverage/coverage-summary.json.
  5. 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.pct path.

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-summary

This 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 add check-coverage: true and thresholds to your config file.
  • Log and export. Combine text-summary (readable in CI logs) with json-summary (parsed by downstream steps): c8 -r text-summary -r json-summary npm test.
  • Post PR comments. Read total.lines.pct from the summary and have a bot comment the number — or the delta against main.
  • 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-summary and 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 --all so untested source files count against your totals instead of silently inflating them.
  • Reporter name typos. It's json-summary, hyphenated — not jsonSummary or json_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 config reports-dir is 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.