Codecov Integration: Coverage Reporting for GitHub, GitLab, and Bitbucket
Code coverage numbers stored locally don't help your team. Coverage becomes useful when it's visible — when every PR shows whether it improved or degraded coverage, when historical trends are trackable, and when coverage gates block regressions before they merge. Codecov is the most widely used service for this, integrating with GitHub, GitLab, and Bitbucket to surface coverage data exactly where code review happens.
How Codecov Works
Codecov doesn't run your tests. It receives coverage reports (LCOV, Cobertura, JaCoCo, or other formats) that your CI pipeline generates, stores them, and produces visualizations. The core workflow:
- Your CI runs tests with coverage enabled
- The CI pipeline uploads the LCOV report to Codecov
- Codecov posts a comment on the PR showing coverage change
- Optional: a coverage gate fails the PR if coverage drops below a threshold
Codecov is free for open-source projects. For private repos, they have a paid tier, though it's generous.
Generating LCOV Reports
Codecov works best with LCOV format. Most JavaScript coverage tools produce it:
Jest:
{
"coverageReporters": ["text", "lcov"]
}Vitest:
coverage: {
reporter: ['text', 'lcov'],
}NYC:
{
"reporter": ["text", "lcov"]
}All three output coverage/lcov.info by default. That's the file you'll upload.
GitHub Integration
Install the Codecov GitHub App from the Codecov website — this enables it to post PR comments and read check statuses. Then add the upload step to your workflow:
# .github/workflows/test.yml
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- name: Run tests with coverage
run: npm test -- --coverage
- name: Upload to Codecov
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage/lcov.info
flags: unittests
name: codecov-umbrella
fail_ci_if_error: trueThe CODECOV_TOKEN secret is found in your Codecov repository settings. For public GitHub repos using the GitHub App, you can often omit the token — Codecov authenticates via the app installation.
After the first upload, Codecov will start posting comments on PRs that look like:
Coverage report: 87.32% (+1.23%) compared to mainWith a breakdown by file showing which changed files gained or lost coverage.
GitLab Integration
For GitLab, Codecov provides a dedicated uploader. The setup is similar but uses GitLab CI syntax:
# .gitlab-ci.yml
test:
stage: test
image: node:20
script:
- npm ci
- npm test -- --coverage
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xml
paths:
- coverage/
codecov:
stage: test
image: node:20
needs: [test]
script:
- npm ci
- npm test -- --coverage
- curl -Os https://uploader.codecov.io/latest/linux/codecov
- chmod +x codecov
- ./codecov --token=$CODECOV_TOKEN --file=coverage/lcov.info --flags=unittestsGitLab also has native coverage parsing — the coverage_report artifact lets GitLab display coverage inline in merge requests without Codecov, using Cobertura format. You can generate both:
{
"coverageReporters": ["text", "lcov", "cobertura"]
}Bitbucket Pipelines
# bitbucket-pipelines.yml
pipelines:
default:
- step:
name: Test and Coverage
image: node:20
script:
- npm ci
- npm test -- --coverage
- pipe: atlassian/codecov-upload:1.0.0
variables:
CODECOV_TOKEN: $CODECOV_TOKEN
FILE: 'coverage/lcov.info'Bitbucket's native coverage support is more limited than GitHub's. Codecov fills the gap by posting pipeline comments.
codecov.yml Configuration
The codecov.yml file in your project root controls Codecov's behavior. This is where you configure coverage targets, PR comment format, and which paths to ignore.
# codecov.yml
coverage:
status:
project:
default:
target: 80% # fail if total coverage drops below 80%
threshold: 1% # allow up to 1% drop before failing
patch:
default:
target: 80% # new code in PRs must have 80% coverage
threshold: 0% # no tolerance for new uncovered code
comment:
layout: "reach,diff,flags,files"
behavior: default
require_changes: true # only comment if coverage changed
ignore:
- "src/generated/**"
- "**/*.d.ts"
- "**/*.stories.tsx"
- "src/main.tsx"
flags:
unittests:
paths:
- src/
carryforward: true # keep previous coverage if this flag doesn't upload
github_checks:
annotations: true # inline coverage annotations in PR file diffThe patch status is particularly valuable. While project tracks overall coverage, patch tracks coverage of the lines changed in the PR. A PR that adds 100 lines of business logic with no tests will show low patch coverage even if the overall project coverage is high. This catches coverage regressions at the point of introduction.
Monorepo Support with Flags
Flags let you track coverage for different parts of a monorepo separately. Each flag has its own coverage graph and can have its own thresholds.
# codecov.yml
flags:
frontend:
paths:
- packages/web/
carryforward: false
backend:
paths:
- packages/api/
carryforward: false
shared:
paths:
- packages/shared/
carryforward: trueIn your CI, upload with the appropriate flag:
# GitHub Actions — monorepo example
jobs:
test-frontend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run test:frontend -- --coverage
- uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: packages/web/coverage/lcov.info
flags: frontend
test-backend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run test:backend -- --coverage
- uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: packages/api/coverage/lcov.info
flags: backendcarryforward: true tells Codecov to use the previous upload for that flag if the current pipeline doesn't upload it. This prevents coverage from appearing to drop to zero when a PR only touches one package.
Coverage Diff in PR Comments
The most valuable part of Codecov's PR comments is the diff view. It shows exactly which files changed coverage and by how much:
Files with missing coverage (-):
src/auth/session.ts 72% → 68% (-4%)
src/payment/stripe.ts 89% → 89% (±0%)
New files with coverage:
src/features/export.ts 91%Files with decreasing coverage show up prominently. This makes it easy for reviewers to ask "why did session.ts drop 4 points?" before the PR merges.
Embedding Coverage Badges
Codecov generates badge URLs from your repository's coverage:
[](https://codecov.io/gh/your-org/your-repo)Badges update after every push to the tracked branch. You can also get flag-specific badges for monorepos:
[](https://codecov.io/gh/your-org/your-repo)Common Pitfalls
Upload before deploy: Upload coverage reports before any deployment steps. If deployment fails, you still want the coverage data from the successful test run.
Parallel test runs: If you run tests in parallel across multiple CI jobs and merge results, each job uploads its own partial report. Use codecov-action with the same commit SHA and Codecov merges them automatically.
Token security: The Codecov token is a repository secret, not a personal access token. Store it in CI secrets and never commit it. For public GitHub repos with the GitHub App installed, the token is optional.
First upload establishes the baseline: The first time you upload, Codecov has nothing to compare against. The PR comment will show the absolute coverage without a delta. Subsequent uploads show trends correctly.
Coverage reporting infrastructure like Codecov is low effort to set up and high value to maintain. Once it's running, coverage trends become part of your team's development culture — visible in every PR, part of every code review conversation.