Codecov Flags vs Components: Splitting Coverage in a Monorepo
A single coverage percentage is unactionable in a monorepo. This guide compares Codecov flags and components, explains carryforward and status-check trade-offs, and shows a validated codecov.yml setup.
30 Jul 2026, 22:09 UTC

A single coverage number for a monorepo is nearly useless. A PR that touches one service gets judged against the whole repository's average, regressions in a low-traffic package hide behind healthy code elsewhere, and nobody knows which team owns a drop. Codecov offers two supported mechanisms to fix this — flags and components — and they solve different problems. Picking the wrong one (or both, redundantly) leads to noisy status checks that teams learn to ignore.
This guide states the decision, compares the two options, and shows a concrete codecov.yml setup with a way to validate it before rolling it out.
The decision and its constraints
The question to answer first: do you need to know which test suite produced the coverage, or which part of the codebase the coverage belongs to?
- Flags are labels attached at upload time (for example,
--flag backendwhen running the Codecov uploader in CI). They answer "which suite produced this." Because they're per-upload, they can describe the same files from different angles — unit tests vs integration tests over the same service. - Components are path-pattern rules defined in
codecov.yml(for example, everything underservices/payments/**). They answer "which part of the codebase changed" and require no changes to your CI upload step.
Constraints that usually drive the choice: whether your CI runs all suites on every commit or only affected ones, whether ownership boundaries follow directory structure, and how many status checks your reviewers will tolerate on a PR.
Comparison at a glance
| Criteria | Flags | Components |
|---|---|---|
| Defined where | At upload time in CI | In codecov.yml |
| Grouping basis | Test suite / upload source | File path patterns |
| Overlapping paths | Supported (same lines can appear under multiple flags) | Possible but risky — overlapping patterns can double-count lines in component reporting |
| Partial CI runs | Carryforward mode reuses last known coverage when a suite didn't run | Not applicable; computed from whatever was uploaded |
| Per-area status checks | Yes, per flag | Yes, per component, with independent targets and thresholds |
| CI changes required | Yes — every upload must be tagged | No |
| Best fit | Unit vs integration splits, conditional test execution | Per-service PR gating aligned with ownership |
Trade-offs that matter in practice
Carryforward is powerful and dangerous. A flag with carryforward: true tells Codecov to reuse the last known coverage for that flag when the suite didn't run on a commit. This is the right tool for monorepos that only run tests for affected services — without it, skipped suites show as missing or zero. But carryforward reports stale coverage as current. Restrict it to suites that genuinely run conditionally; never enable it for suites that run on every commit, or you'll mask real gaps.
Components align with review, flags align with execution. A reviewer looking at a PR that touches services/payments/ wants to know whether payments coverage regressed — that's a component. Whether the payments integration suite specifically dropped is a flag question. If your directory structure matches team ownership, components give you per-service gating with zero CI changes.
Strict targets create ignored checks. Both mechanisms feed Codecov status checks configured with target (the required coverage level) and threshold (allowed drop). A target of 100% or a threshold of zero on legacy code guarantees red checks on unrelated PRs, and teams learn to merge through them. Start with target: auto and a small threshold like 1–2%, then tighten per area.
Renaming breaks history. Flag and component names are the keys Codecov uses for historical comparison. Decide names up front and treat them as stable identifiers.
A practical default configuration
For most monorepos: components for per-service status checks, flags only where you need carryforward or a unit/integration split. The example below assumes a repo with services/payments/ and services/search/, where the payments integration suite runs only when payments code changes.
# codecov.yml at the repository root
coverage:
status:
project:
default:
target: auto
threshold: 2%
component_management:
individual_components:
- component_id: payments
name: Payments service
paths:
- services/payments/**
statuses:
- type: project
target: auto
threshold: 1%
- component_id: search
name: Search service
paths:
- services/search/**
statuses:
- type: project
target: auto
threshold: 1%
flags:
payments-integration:
carryforward: trueKeep component path patterns mutually exclusive where possible — if a shared library lives under services/common/, either give it its own component or accept that it appears in whichever component's pattern matches it, and check the component view in the Codecov UI for double-counted lines.
On the CI side, tag uploads explicitly. Where you run the Codecov uploader (this requires the CI job's checkout and a Codecov token configured as a secret):
# Example: inside the CI step that runs the payments integration suite
# Requires CODECOV_TOKEN in the environment
codecov --flag payments-integration --file coverage/payments-integration.xmlThe exact uploader command and flags depend on which uploader version your pipeline uses; verify against the documentation for your setup rather than copying blindly.
Validate before rolling out
Don't enable strict gating on day one. Validate the configuration with a throwaway PR:
- Merge the
codecov.ymlchange with status checks set to informational (or simply observe them without branch protection requiring them). - Open a small PR touching only
services/payments/. Confirm that the Payments component status posts and the Search component status either doesn't post or stays unaffected. - Push a commit that changes no payments code, so the integration suite is skipped. In the Codecov UI's flags view, confirm
payments-integrationshows carried-forward coverage rather than disappearing. - Deliberately delete or weaken a test in one service on a scratch branch and confirm only that component's check fails.
If a component status never appears, check that the path patterns match the report's file paths as Codecov sees them — a common cause is a mismatch between CI working-directory prefixes and the patterns in codecov.yml. If carryforward doesn't trigger, confirm the flag name in the upload exactly matches the flags: key.
Limitations
Component and flag YAML keys, and which UI features are available, vary by Codecov plan tier and evolve over time — check the syntax against the documentation version matching your plan before committing to a large rollout. Status check names also depend on your GitHub app configuration, so branch-protection rules referencing them need updating if you rename anything. Treat the example above as a starting structure, not a copy-paste guarantee.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.