buildstats.io

Code coverage badge and history chart without Codecov

buildstats takes the coverage percentage your tests print and serves a README badge, a per-commit chart and a pull request comment with the delta. Free for public repos, no signup; private $9/month per owner.

Codecov and Coveralls parse your coverage report, which gives line-by-line views and patch coverage. Badge actions commit an SVG to the repository or a gist, which gives a badge and nothing else. buildstats does the middle thing: a number in, and out come a badge, a chart and a pull request delta, with no report upload and no commit to your repository.

Setup: extract the percentage, push it

Every coverage tool prints a total. The example uses a JSON summary from Istanbul or Vitest; the jq line is the only thing that changes for other tools.

name: Test

on:
  push:
    branches: [main]
  pull_request:

permissions:
  contents: read
  id-token: write
  pull-requests: write

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci && npx vitest run --coverage --coverage.reporter=json-summary

      - id: cov
        run: |
          echo "lines=$(jq '.total.lines.pct' coverage/coverage-summary.json)" >> "$GITHUB_OUTPUT"
          echo "branches=$(jq '.total.branches.pct' coverage/coverage-summary.json)" >> "$GITHUB_OUTPUT"

      - uses: bitgate/buildstats@v1
        with:
          metrics: |
            coverage=${{ steps.cov.outputs.lines }} % higher
            branch_coverage=${{ steps.cov.outputs.branches }} % higher
          max-regression: 1

Other tools: coverage.py prints the total with coverage report --format=total; go tool cover -func ends with a total: line; cargo llvm-cov --summary-only --json has .data[0].totals.lines.percent; JaCoCo's XML has counters you can sum with xmllint; simplecov writes .last_run.json with result.line. The direction higher makes a drop show as worse, and max-regression: 1 fails the step when coverage falls more than 1% below the base branch.

The badge

![coverage](https://buildstats.io/acme/app/coverage/badge.svg)

The badge is a 20-pixel SVG in the shields.io flat style showing the latest value on the default branch, for example "coverage 87.2%". It updates by itself after each push; public badges are cached for 5 minutes. /coverage/badge.json serves the same number in the shields.io endpoint format, for a badge rendered by shields.io with your own colours and logo. ?ref=release shows a different branch.

The chart and the pull request comment

<picture>
  <source media="(prefers-color-scheme: dark)" srcset="https://buildstats.io/acme/app/coverage.svg?theme=dark">
  <img alt="line coverage per commit" src="https://buildstats.io/acme/app/coverage.svg">
</picture>

The chart shows up to 1,000 of the 50,000 points a metric can hold, 400 to 1,200 pixels wide, in light and dark. On pull requests the Action compares the coverage with the latest value on the base branch and posts one comment with the base value, the new value and the delta. The comment is kept up to date across pushes and matrix legs. With max-regression set, the step fails after the comment is written, so a required check blocks the merge while the numbers stay visible.

Coverage per package pushes as variants: variant: ${{ matrix.package }} stores coverage/api and coverage/web, which overlay on one chart and get separate rows in the comment.

Limits and pricing

Public repositoriesPrivate repositories
PriceFree$9/month per GitHub user or organization
TrialNot needed14 days, no card
Projects per owner10025
Metrics per project100100
Points per metric and branch50,00050,000
Metrics per push5050
Badge and chart imagesPublic URLs, cached 5 minutesUnguessable token URLs, cached 1 minute
MembersUnlimitedUnlimited

Everything else is on the pricing page.

How this compares

ToolWhat it storesBadgeHistory chartPull requestPrice
buildstatsThe percentageYes, servedYes, in the READMEComment with delta, max-regression gateFree public, $9/month per owner private
CodecovFull report, line levelYesTrend views in the appPatch and project coverage commentFree for open source; Developer plan free with 250 private uploads/month, paid tiers above
CoverallsFull report, line levelYesIn the appComment and statusFree for open source; paid per organization for private
GitHub code coverage (actions/upload-code-coverage)Cobertura XMLNoDefault-branch scansComment from github-code-quality botPer active committer on Team or Enterprise
schneegans/dynamic-badges-actionJSON in a gistVia shields.io endpointNoNoFree
tj-actions/coverage-badge-pySVG committed to the repoYes, from the repoNoNoFree

Choose Codecov or Coveralls when reviewers need to see which lines a pull request left uncovered. Choose a gist or committed badge when the badge is all you want and a bot commit per run is acceptable. Choose buildstats when you want the badge, the per-commit chart and the pull request delta from one number and no uploads.

Questions

Does buildstats show which lines are uncovered?

No. It stores the percentage, not the report. Use Codecov, Coveralls or your coverage tool's HTML report for line-level views, and buildstats for the badge, the history and the pull request delta.

Which coverage tools work?

Any that prints a total: Istanbul, Vitest, Jest, coverage.py, pytest-cov, go tool cover, cargo llvm-cov, tarpaulin, JaCoCo, simplecov, gcov, lcov. One jq, grep or xmllint line turns the total into a metric.

Can the badge show a branch other than the default?

Yes. Add ?ref=release to the badge or chart URL.

Can I fail a pull request that lowers coverage?

Yes. Set max-regression on the step and require the check in branch protection. The step fails when coverage drops by more than that percentage against the base branch.

Does this add a commit to my repository?

No. The badge and the chart are served from buildstats.io and update after each push; nothing is written to your repository or a gist.

Last updated 2026-10-07.