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

pnpm Workspace Filtering

Core Mechanics of pnpm Workspace Filtering

pnpm resolves workspace boundaries via pnpm-workspace.yaml. The package manager constructs a directed acyclic graph (DAG) from declared globs, enabling deterministic dependency resolution without external task runners. This native graph traversal reduces CI compute by executing commands strictly within the resolved subgraph.

Workspace Definition

# pnpm-workspace.yaml
packages:
 - 'packages/*'
 - 'apps/*'
 - '!**/test/**'

Execution Semantics

  • Graph Resolution: pnpm reads package.json name fields and workspaces declarations, mapping internal workspace:* or workspace:^ protocols to absolute paths.
  • CLI Precedence: --filter overrides root-level pnpm run execution. If no packages match, pnpm exits with code 0 (unless --fail-if-no-match is passed in v8+).
  • Lifecycle Hooks: Filters scope execution but preserve standard npm lifecycle order (preinstallinstallpostinstallbuild).

For foundational graph topology strategies, review Monorepo Architecture & Orchestration before scaling workspace boundaries.


Explicit Filter Syntax & Dependency Scoping

Filter syntax operates at the CLI layer, bypassing daemon overhead. Proper quoting is mandatory to prevent premature shell glob expansion.

Targeting Patterns

# 1. Exact Name Match
pnpm --filter @my-org/design-system build

# 2. Path-Based Match (relative to workspace root)
pnpm --filter './packages/ui-lib' build

# 3. Upstream Traversal (Target + All Dependencies)
pnpm --filter '...@my-org/api' build

# 4. Downstream Traversal (Target + All Dependents)
pnpm --filter '@my-org/core...' test

# 5. Multi-Filter Chaining (Union)
pnpm --filter @my-org/core --filter @my-org/utils lint

Platform Comparison

While Turborepo Pipeline Configuration and Nx Workspace Architecture provide remote caching and distributed task graphs, pnpm's zero-daemon filtering is optimal for:

  • Strictly package-focused repositories
  • Lightweight CI runners where daemon memory overhead is constrained
  • Environments requiring deterministic, package-manager-native execution guarantees

Production CI/CD Integration & Change Detection

Wire filters into pipelines using git diff to compute affected packages, then feed results into parallel runners. Always validate outputs against an allowlist to prevent arbitrary execution.

Dynamic Change Detection Pipeline

#!/usr/bin/env bash
set -euo pipefail

# 1. Identify changed directories since last commit
CHANGED_DIRS=$(git diff --name-only origin/main...HEAD | awk -F'/' '{print $1"/"$2}' | sort -u)

# 2. Map directories to pnpm workspace packages
FILTER_ARGS=""
for dir in $CHANGED_DIRS; do
 PKG=$(pnpm list --json --filter "./$dir" --depth -1 | jq -r '.[0].name // empty')
 if [[ -n "$PKG" ]]; then
 FILTER_ARGS+="--filter $PKG "
 fi
done

# 3. Security: Validate against organization allowlist
ALLOWLIST_REGEX="^@my-org/"
if [[ ! "$FILTER_ARGS" =~ $ALLOWLIST_REGEX ]]; then
 echo "⛔ Filter output contains unauthorized scopes. Aborting."
 exit 1
fi

# 4. Execute in parallel (CI matrix)
if [[ -n "$FILTER_ARGS" ]]; then
 pnpm --filter $FILTER_ARGS lint
 pnpm --filter $FILTER_ARGS test
else
 echo "✅ No workspace packages affected. Skipping."
fi

GitHub Actions Matrix Generation

jobs:
 workspace-test:
 runs-on: ubuntu-latest
 strategy:
 matrix:
 package: ${{ steps.detect.outputs.packages }}
 steps:
 - uses: actions/checkout@v4
 - uses: pnpm/action-setup@v3
 - run: pnpm --filter ${{ matrix.package }} test

Security & Caching Controls

  • Registry Scope Locking: Enforce .npmrc at the workspace root to prevent token leakage.
# .npmrc
@my-org:registry=https://npm.pkg.github.com/
//npm.pkg.github.com/:_authToken=${NPM_TOKEN}
  • Cache Key Generation: Hash filtered package lockfiles to avoid stale cache hits.
pnpm --filter $FILTER_ARGS install --frozen-lockfile
echo "cache-key=$(pnpm --filter $FILTER_ARGS list --json | sha256sum | cut -d' ' -f1)" >> $GITHUB_ENV

Align filtered execution with artifact retention policies by referencing Using pnpm --filter for Targeted Builds for advanced cache alignment.


Targeted Publishing & Version Bump Workflows

Combine --filter with semantic versioning tools to enforce scoped, auditable releases. Never execute pnpm publish without pre-flight validation gates.

Scoped Release Pipeline

# 1. Pre-flight validation
pnpm --filter '@my-org/*' run lint:strict
pnpm --filter '@my-org/*' run test:ci

# 2. Version bump (using @changesets/cli)
npx changeset version
pnpm install --frozen-lockfile

# 3. Filtered publish to private registry
pnpm --filter '@my-org/*' publish --access restricted --tag next

Security Controls for Publishing

Control Implementation
Private Package Isolation Set "private": true in root package.json. Only publish explicit workspace packages.
Auth Token Scoping Use NPM_TOKEN with publish scope only. Never use read/write tokens in CI.
Dry-Run Validation Always execute pnpm --filter <scope> publish --dry-run before production pushes.
Semver Enforcement Integrate changeset or release-please to auto-generate CHANGELOG.md and prevent manual version drift.

Common Pitfalls & Security Anti-Patterns

Mistake Impact Remediation
Unquoted globs/ellipsis Shell expands ... or * before pnpm parses, causing ENOENT errors. Always wrap filters in single quotes: --filter '...@scope/pkg'
Assuming hooks are skipped --filter scopes execution but runs pre/post hooks sequentially. Use --ignore-scripts if lifecycle hooks must be bypassed in CI.
Ignoring peer dependencies Downstream filters omit implicit peers, breaking type resolution. Run pnpm install --filter '...' before builds to materialize peer links.
Hardcoded CI package lists Wastes compute, causes stale cache hits, misses dependency impacts. Implement git diff + pnpm list dynamic detection.
Unscoped .npmrc auth Leaks internal packages to public npm on filtered publish. Scope tokens via @org:registry= and enforce publishConfig.access=restricted.

FAQ

How does pnpm --filter differ from Turborepo or Nx task runners? pnpm filtering operates natively at the package manager layer, resolving workspace graphs without a background daemon. It provides lightweight, deterministic execution but lacks built-in remote caching and distributed task orchestration found in dedicated runners.

Can I safely use pnpm --filter with private npm registries? Yes. pnpm respects per-workspace .npmrc configurations. Ensure registry auth tokens are explicitly scoped to your organization, and validate filter outputs before execution to prevent unauthorized package resolution or publishing.

Why does --filter '...' sometimes include unexpected packages? The ellipsis syntax traverses the full dependency graph to guarantee build integrity. If a package has implicit peer dependencies, workspace aliases, or optional dependencies, pnpm will include them. Use explicit package lists or --depth constraints to narrow scope.

How do I enforce security boundaries when filtering in CI? Implement package allowlists, validate filter outputs against a signed workspace manifest before execution, and enforce strict registry access controls. Never allow untrusted filter inputs to trigger publish or install commands.