Using Codecov Flags to Track Coverage per Package in a Monorepo
Learn how to define flags in codecov.yml, upload separate coverage reports, and use carryforward to keep trends alive for unchanged parts of a monorepo.
12 Oct 2025, 03:09 UTC

The problem: one coverage number hides real risk
In a monorepo you might run unit tests for only the packages that changed in a CI job. If you upload a single coverage report, Codecov shows a repository‑wide percentage that can go up even when a neglected package loses tests, because the unchanged packages keep their old numbers. Teams need a way to see coverage trends for each logical area and to enforce limits where they matter.
Thesis: Codecov flags give you separate tracks and optional carryforward
By declaring flags in codecov.yml and attaching them at upload time, each flagged area gets its own coverage graph, status check, and trend line. Carryforward lets Codecov reuse the last known value for a flag when a commit does not upload a new report, which is ideal for monorepos where only a subset of packages is exercised per run.
Defining flags
Create or edit codecov.yml at the repository root:
coverage:
precision: 2
round: down
flags:
unit:
# tracks unit‑test coverage
carryforward: true
integration:
# tracks integration‑test coverage
carryforward: false
The flag names (unit, integration) are stable identifiers; they must be lowercase and match what you pass to the uploader.
Uploading with flags in CI
Assuming you use the official Codecov GitHub Action, you can split the coverage step by package or test type. Example for a monorepo with two frontend packages:
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install deps
run: npm ci
- name: Run unit tests
run: npm run test:unit -- --coverage --outputFile=coverage-unit.json
- name: Upload unit coverage
uses: codecov/codecov-action@v4
with:
files: coverage-unit.json
flags: unit
name: codecov-unit
fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }}
- name: Run integration tests
run: npm run test:integration -- --coverage --outputFile=coverage-integration.json
- name: Upload integration coverage
uses: codecov/codecov-action@v4
with:
files: coverage-integration.json
flags: integration
name: codecov-integration
fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }}
Where to run: in your CI environment (GitHub Actions, GitLab CI, etc.). Required permissions: the job must be able to read the repository and have access to the CODECOV_TOKEN secret. Expected checks: after each upload, Codecov shows two separate coverage tracks labeled “unit” and “integration”. Risks: mismatched flag names between jobs create separate, unrelated tracks, fragmenting history.
Worked example: verifying carryforward
- Commit A: upload both
unitandintegrationreports. Codecov displays 78% unit, 65% integration. - Commit B: modify only
pkg-aand run unit tests; the integration step is skipped. Upload only theunitflag. - In the Codecov UI, the
unittrack updates with the new number (say 80%). Theintegrationtrack shows the same 65% as Commit A, marked with a small “carryforward” icon indicating the value was reused. - If you later run integration tests again and upload a lower number, the integration track will reflect the real change.
Practical way to check: open the commit page in Codecov, hover over the integration point for Commit B, and confirm the tooltip says “carryforward from commit ”.
Trade‑off and limitation
Carryforward can hide a silent drop in coverage if a package stops uploading its report altogether. Because the old value persists, the trend line looks healthy even though no new data has been submitted. To mitigate this, monitor the upload count per flag (available in the “Uploads” tab) or enable the “Require upload” option for critical flags, which forces a failure when a commit lacks a report for that flag.
Actionable closing
Start by adding a flags section to codecov.yml that matches the logical boundaries of your monorepo (packages, layers, or test types). In your CI, attach the appropriate flag to each coverage upload and enable carryforward for flags that you expect to be sparse. After a few runs, verify that each flag appears as a distinct track and that carryforward behaves as expected. This gives you granular, actionable coverage insight without inflating CI complexity.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.