Using Codecov Flags to Isolate Coverage in a Monorepo
Learn how Codecov flags let you split coverage uploads by service or test suite, apply per‑flag thresholds, and get precise PR status checks without duplicating CI pipelines.
01 Dec 2025, 21:41 UTC

The problem: one coverage number hides service‑specific regressions
When a monorepo contains multiple independently deployable services, a single overall coverage percentage can mask a drop in one area while another improves. Teams that rely on a global threshold miss PR‑level warnings for the service that actually changed, leading to delayed detection of insufficient tests.
Thesis: Codecov flags let you split uploads by logical component and apply per‑flag thresholds, giving precise visibility without splitting CI pipelines.
Flags are a lightweight way to tell the Codecov uploader which logical group a coverage report belongs to. The platform then maintains separate totals, badges, and status checks for each flag while still allowing a combined view for repository‑wide health.
How flags work in codecov.yml
Define the flags you want under a top‑level flags: block. Each flag can be left empty (defaults) or tuned with its own fail_under value.
# .codecov.yml
coverage:
precision: 2
round: down
flags:
api:
fail_under: 85 # require API tests to stay above 85%
web:
fail_under: 80 # web UI can tolerate a slightly lower threshold
# optional: comment behavior per flag
comment:
layout: "reach, diff, flags"
behavior: default
When the uploader runs, you pass one or more flag names via --flags. A single CI job can upload multiple reports (e.g., from parallel test suites) by invoking the uploader several times with different flag values.
Worked example: CI for a two‑service monorepo
Assume a repository with services/api/ and services/web/. Each service has its own test command that produces a coverage file in coverage/.
In a GitHub Actions workflow (similar steps apply to GitLab CI, Jenkins, or a plain bash script):
# .github/workflows/coverage.yml
name: Coverage
on:
pull_request:
branches: [ main ]
jobs:
upload:
runs-on: ubuntu-latest
permissions:
contents: read # to checkout the repo
pull-requests: write # to post Codecov PR comment
steps:
- uses: actions/checkout@v4
- name: Set up Node (example)
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Test API service
run: |
cd services/api
npm run test -- --coverage --coverageDirectory=../../coverage/api
- name: Test Web service
run: |
cd services/web
npm run test -- --coverage --coverageDirectory=../../coverage/web
- name: Upload API coverage to Codecov
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
run: |
bash <(curl -s https://codecov.io/bash) \
-F api \
-t $CODECOV_TOKEN \
-f coverage/api/coverage-final.json
- name: Upload Web coverage to Codecov
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
run: |
bash <(curl -s https://codecov.io/bash) \
-F web \
-t $CODECOV_TOKEN \
-f coverage/web/coverage-final.json
Where to run: The workflow runs on the CI platform that has access to the repository and a CODECOV_TOKEN secret (read‑only token is sufficient for uploads). No special privileges beyond checking out code and posting PR comments are needed.
Expected checks: After the workflow finishes, visit the pull request on GitHub. You should see two separate Codecov status checks: one for api and one for web. Each will be green if the coverage for that flag meets its fail_under threshold, red otherwise.
Verification steps you can perform:
- Add the
flags:block to.codecov.ymlas shown. - Run the workflow on a branch where you deliberately remove a test from
services/api. - Confirm that the
apiflag check fails while thewebflag check stays green. - Visit the badge URL
https://img.shields.io/codecov/c/github/<user>/<repo>/main?flag=apiand see the percentage reflect only the API coverage.
Trade‑off and limitation
The primary operational cost is maintaining consistent flag names across every upload. A typo (e.g., --flags ap instead of --flags api) creates a new, unintended flag, fragmenting coverage data and making thresholds harder to interpret. Teams mitigate this by centralizing the flag list in codecov.yml and using CI linting steps that validate the --flags argument against that list.
Another constraint is the free tier limit on unique flags per repository (commonly 10). Large monorepos with many micro‑services may need to consolidate related services under broader flags or upgrade to a paid plan for higher limits.
Actionable closing
Start small: pick two logically separate parts of your codebase, define flags for them, and add the --flags argument to your existing uploader calls. Observe the split status checks in your next PR; if they surface a regression that the global metric hid, you’ve gained the immediate value flags provide. Over time, extend the flag set to match your service boundaries, and keep the flag names in a single source of truth to avoid fragmentation.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.