Skip to content

/commands/pnpm/pnpm-filter

pnpm --filter: run a script in one workspace package

verified onenvironmentlocal · windows 10.0.26200 arm64 · GNU bash 5.3.15 (Git Bash)updated
tool
pnpm
versions
pnpm 8 – 10
os
any

pnpm --filter <selector> <command> runs a command only in the workspace packages that match the selector: pnpm --filter @acme/api run build builds one package, and pnpm --filter "@acme/web..." run build builds @acme/web and every workspace package it depends on, in dependency order. Without a filter, pnpm -r run build runs the script in all packages.

Example workspace

bash
cat > package.json <<'EOF'
{ "name": "monorepo", "private": true }
EOF
cat > pnpm-workspace.yaml <<'EOF'
packages:
  - "packages/*"
EOF
mkdir -p packages/api packages/web
cat > packages/api/package.json <<'EOF'
{ "name": "@acme/api", "version": "1.0.0", "scripts": { "build": "echo build api" } }
EOF
cat > packages/web/package.json <<'EOF'
{ "name": "@acme/web", "version": "1.0.0", "scripts": { "build": "echo build web" },
  "dependencies": { "@acme/api": "workspace:*" } }
EOF
npx --yes pnpm@9 install > /dev/null 2>&1
echo "--- --filter @acme/api"
npx --yes pnpm@9 --filter @acme/api run build | grep -v '^>'
echo "--- --filter @acme/web..."
npx --yes pnpm@9 --filter "@acme/web..." run build

Output

--- --filter @acme/api

build api
--- --filter @acme/web...
Scope: 2 of 3 workspace projects
packages/api build$ echo build api
packages/api build: build api
packages/api build: Done
packages/web build$ echo build web
packages/web build: build web
packages/web build: Done

With one matching package pnpm prints the script output directly; with several it prefixes each line with the package path and runs them in topological order (dependencies first).

Selector syntax

SelectorMatches
@acme/apiThe package with that name
"@acme/*"Every package whose name matches the glob
"@acme/web..."The package and all its dependencies
"...@acme/api"The package and all packages that depend on it
"@acme/web^..."Only the dependencies, not the package itself
./packages/apiThe package in that directory (a path starts with . or /)
"{packages/**}"Packages under a directory glob
"[origin/main]"Packages changed since that git ref
"!@acme/docs"Exclude a package (combine with other filters)

When nothing matches

bash
cat > package.json <<'EOF'
{ "name": "monorepo", "private": true }
EOF
cat > pnpm-workspace.yaml <<'EOF'
packages:
  - "packages/*"
EOF
mkdir -p packages/api
cat > packages/api/package.json <<'EOF'
{ "name": "@acme/api", "version": "1.0.0", "scripts": { "build": "echo build api" } }
EOF
npx --yes pnpm@9 install > /dev/null 2>&1
npx --yes pnpm@9 --filter @acme/apii run build 2>&1 | sed 's/ in ".*"$//'
echo "exit=${PIPESTATUS[0]}"
npx --yes pnpm@9 --filter @acme/apii --fail-if-no-match run build 2>&1 | sed 's/ in ".*"$//'
echo "exit=${PIPESTATUS[0]}"

Output

No projects matched the filters
exit=0
No projects matched the filters
exit=1

A misspelled selector is not an error by default, so a CI step can pass without building anything. Add --fail-if-no-match to CI commands, or set failIfNoMatch in the workspace settings.

Quote selectors that contain *, ..., ^, ! or brackets so the shell does not expand them. Repeat --filter to combine selectors: the result is the union.

Other commands

--filter works with most commands, not only run:

bash
pnpm --filter @acme/web add zod          # add a dependency to one package
pnpm --filter "./packages/**" test       # run tests in every package under packages/
pnpm --filter "...[origin/main]" build   # build changed packages and their dependents

Options used with filters

OptionEffect
-r, --recursiveRun in every workspace package (no filter needed)
--filter-prod SELECTORLike --filter, but ignores devDependencies when following the ... dependency selectors
--fail-if-no-matchExit with an error when no package matches
--parallelRun the script in all matched packages at once, ignoring dependency order (for watch scripts)
--workspace-concurrency=NHow many packages run at the same time (default 4)
--changed-files-ignore-pattern GLOBWith a [ref] selector, ignore changes in matching files (docs, READMEs)
--test-pattern GLOBWith a [ref] selector, treat matching files as tests so only the changed package is selected, not its dependents

Typical CI use

Building only what a pull request touched keeps monorepo pipelines short: pnpm --filter "...[origin/main]" build selects every package changed since origin/main plus everything that depends on them, so a change in a shared library rebuilds its consumers too. The git ref must exist in the CI checkout, so fetch it first (a shallow clone often has no origin/main).

FAQ

Why does pnpm --filter run nothing and still succeed?

No package matched the selector, often a typo in the package name or a path without a leading ./. pnpm prints No projects matched the filters and exits 0; add --fail-if-no-match to make it fail.

Should I use the package name or the folder in --filter?

Either works. The name (from package.json) is stable when folders move; a path must start with . or / so pnpm treats it as a directory, for example --filter ./packages/api.

How do I run a script in the workspace root instead of a package?

Use -w (--workspace-root), for example pnpm -w run lint. --filter selects workspace packages, not the root.

Sources