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.jsonnamefields andworkspacesdeclarations, mapping internalworkspace:*orworkspace:^protocols to absolute paths. - CLI Precedence:
--filteroverrides root-levelpnpm runexecution. If no packages match, pnpm exits with code0(unless--fail-if-no-matchis passed in v8+). - Lifecycle Hooks: Filters scope execution but preserve standard npm lifecycle order (
preinstall→install→postinstall→build).
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
.npmrcat 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.