Back to monorepo orchestration Target affected workspaces Configure turbo pipelines Compare the Nx approach

Using pnpm --filter for Targeted Builds

A targeted build runs only the packages a change actually affects, instead of rebuilding the whole repository. pnpm's --filter flag delivers that natively, but it fails in confusing ways when the selector does not resolve: either it errors out, or worse, it silently falls back to running everywhere. This page covers the exact symptoms, the root cause, a step-by-step recovery workflow, and the CI guardrails that keep targeted builds targeted. It builds directly on pnpm Workspace Filtering.

Exact Symptoms

ERR_PNPM_FILTER_NO_MATCH  No projects matched the filters in "/repo"

Other signals that filtering has gone wrong:

  • Builds execute on packages you never named, and CI duration creeps back up toward a full-workspace run.
  • A --filter '...[origin/main]' selection resolves to zero packages even though files changed.
  • Cascading "cannot find module" failures because a targeted build skipped a dependency it needed.
Change-aware filter resolving a git range to a build set A git base ref diff yields the changed packages, the upstream operator adds their dependencies, and pnpm builds only that set. ...[origin/main] git range diff changed + deps resolved package set build runs here nothing else Full git history is required for the diff to resolve correctly.
Change-aware filtering diffs against a base ref, adds upstream dependencies, and builds only the resulting set.

Root Cause

ERR_PNPM_FILTER_NO_MATCH and silent full-workspace fallback both trace to the same thing: the selector did not resolve against the workspace graph. The usual causes are misconfigured boundaries in pnpm-workspace.yaml, a package whose package.json name does not match the pattern, a missing traversal operator (...) so dependencies are excluded, or — for change-aware filters — a shallow clone that gives the git diff nothing to compare against. Understanding the underlying resolution model in pnpm Workspace Filtering is what keeps these failures from recurring.

Diagnostic & Recovery Workflow

1. Validate workspace boundaries

Confirm the manifest exists at the repository root and declares every package directory.

# pnpm-workspace.yaml
packages:
  - 'packages/*'
  - 'apps/*'
pnpm list --recursive --depth=0

This lists every package pnpm recognizes. If a package you expected is absent, its directory is not covered by a glob, or its package.json has no name.

2. Test filter resolution with a dry run

Resolve the filter with pnpm list before you attach an expensive build to it. list is read-only, so it is safe to iterate on.

# Exact name
pnpm list --filter "my-package" --recursive

# Upstream: target + all dependencies
pnpm list --filter "...my-package" --recursive

# Downstream: target + all dependents
pnpm list --filter "my-package..." --recursive

3. Run the targeted build

Once the selector lists the packages you expect, attach the build. Use the upstream operator so dependencies compile first.

pnpm --filter "...my-package" run build

Read the terminal output and confirm only the intended package and its upstream dependencies executed.

Required Configuration Baseline

  • pnpm-workspace.yaml must sit at the repository root and cover every package directory.
  • package.json in every package needs a valid, unique name that matches the filter pattern you intend to use.
  • .npmrc should set auto-install-peers=true so peer dependencies resolve consistently across filtered scopes.

CI/CD Safeguards

  • Change-aware filtering. Replace bare pnpm run build with pnpm --filter '...[origin/main]' run build so only modified packages and their dependencies build.
  • Full history. Set fetch-depth: 0 on actions/checkout — a shallow clone makes the git range resolve to nothing.
  • Fail on empty match. Add --fail-if-no-match (pnpm v8+) so a typo errors instead of silently building nothing.
  • Production-only installs. Use pnpm install --prod in deploy steps to keep devDependencies out of the resolution graph.
  • Pre-commit consistency. Run pnpm install --frozen-lockfile and pnpm list --recursive --depth=0 in a hook to catch a broken workspace before it reaches CI.

Frequently Asked Questions

What is the difference between pnpm --filter my-package and pnpm --filter ...my-package? The ... prefix turns on upstream dependency-aware filtering, so pnpm builds the target plus every workspace dependency it needs — which prevents missing-module errors during compilation. Without the prefix, only the exact named package runs.

Why does pnpm --filter build packages I did not specify? You used a traversal operator. ...pkg pulls in the upstream dependency chain and pkg... pulls in the downstream dependent chain. Drop the operators and pass an exact name to restrict execution to a single package.

Why does --filter '...[origin/main]' build nothing in CI? The runner cloned shallowly, so the diff against origin/main has no history to compare. Set fetch-depth: 0 on the checkout step, and make sure the base ref is actually fetched.

Can I combine pnpm --filter with Turborepo or Nx? You can, but it is usually redundant — both provide their own task graph and caching. Reserve pnpm --filter for raw script execution or for bypassing the orchestrator on a single targeted install or audit.

Related

pnpm Workspace Filtering