pnpm run routing
Most tutorials show a command like pnpm run dev and move on. This page is the missing grammar: what each word means, where the script name is defined, and how to stay at the repo root in a monorepo.
Command shape
A typical invocation looks like this:
pnpm [global flags] run <script-name> [-- script-args]
| Token | Meaning |
|---|---|
|
The package manager binary (installed via Corepack or otherwise on your PATH). |
Global flags (optional) |
Options that apply before the subcommand — e.g. |
|
Subcommand: execute a named script from a |
|
A key under |
|
Arguments passed through to the underlying command (everything after |
Where dev comes from
Yes: dev is almost always a script name in package.json, not a built-in pnpm target.
{
"name": "site-nextjs",
"scripts": {
"dev": "next dev --turbopack",
"build": "next build",
"lint": "next lint"
}
}
When you run pnpm run dev (or pnpm dev), pnpm:
-
Finds the target package (see [root-first] below).
-
Looks up
"dev"in that package’s"scripts". -
Runs the associated shell command (
next dev --turbopackhere) with that package as the working directory.
If the script key does not exist, pnpm fails — there is no universal dev script across all Node projects. Some repos use start, serve, or only build. Always check package.json → scripts.
Single package vs monorepo
Single-package repo
If the repo has one package.json at the root and no workspace:
pnpm install # from repo root
pnpm dev # runs root package.json → scripts.dev
Your mental model holds: clone, install once at root, run pnpm dev.
Monorepo (pnpm workspace)
A workspace adds pnpm-workspace.yaml listing package directories. pnpm treats each listed folder plus the repo root as separate packages, each with its own package.json.
# pnpm-workspace.yaml
packages:
- "site-nextjs"
- "shared"
Implications:
-
The root
package.jsonmay have nodevscript (only deploy helpers, formatters, etc.). -
The app you care about (
site-nextjs,apps/web, …) definesdevin itspackage.json. -
Running bare
pnpm devat the root fails if rootscriptshas nodevkey — that is expected, not a broken install.
Shop default: install from the repo root (pnpm install), but route run commands to the app package without cd ([root-first]).
Root-first: run from the repo root
Once you are at the repo root, you should not need to cd into site-nextjs/ (or similar) for day-to-day scripts. Two pnpm mechanisms select the target package:
--filter / -F (workspace selector)
Exact meaning: run this command in one or more workspace packages pnpm knows about from pnpm-workspace.yaml, matched by package name or path pattern.
pnpm --filter site-nextjs dev
pnpm -F site-nextjs dev # short form
-
Uses the
"name"field in the targetpackage.json(site-nextjs) or a path glob (./site-*). -
Workspace-aware: can target dependents/dependencies with graph suffixes (
…— see Filter graph suffixes (optional)). -
Preferred when you want monorepo semantics (names, patterns, multiple packages).
-C / --dir (directory override)
Exact meaning: behave as if pnpm’s current working directory were this filesystem path before running the subcommand.
pnpm -C site-nextjs dev
pnpm --dir site-nextjs dev
-
Takes a path, not necessarily a workspace name.
-
Does not understand workspace dependency graphs.
-
Equivalent to
cd site-nextjs; pnpm devfor a single folder — but you stay in the shell at repo root.
For one app package, -C and --filter usually do the same thing. Prefer --filter when you might later use patterns or graph selectors; prefer -C when you just mean “that folder’s package.json.”
Side-by-side
--filter / -F |
-C / --dir |
|
|---|---|---|
Selects by |
Workspace package name or glob |
Filesystem path |
Monorepo graph ( |
Yes |
No |
Works outside a workspace |
No (needs workspace) |
Yes (any directory with package.json) |
Typical use |
|
|
Filter graph suffixes (optional)
| Pattern | Effect |
|---|---|
|
That package only. |
|
Package and its workspace dependencies (build order helpers). |
|
Package and workspace dependents (things that depend on it). |
|
All packages matching the path glob. |
Recipes (stay at repo root)
| Goal | Command (from repo root) |
|---|---|
Install entire workspace |
|
Dev server for one app |
|
Build one app |
|
Run a bin from a package (e.g. Convex CLI) |
|
Lint only the app package |
|
Run root-only script (if defined) |
|
Epistemology checklist (how to read a tutorial command)
When a blog post throws pnpm run dev at you, ask:
-
Which package? Root or nested? In a monorepo,
devmay not live at root. -
Where is the script defined? Open that package’s
package.json→"scripts". -
What does the script actually run? The value is the real command (
next dev,vite,tsx watch, …). -
Where should I stand? Repo root +
--filter/-C, or genuinely single-package root. -
Is
runrequired? No for standard script names;pnpm devandpnpm run devare the same.
Related commands (not run)
| Command | Role |
|---|---|
|
Run a dependency binary from the target package’s |
|
One-off download + run (like |
|
Always workspace-wide from repo root in monorepos; do not |
Related
-
pnpm mechanics — store, hoisting, why we prefer pnpm
-
npm package managers — pnpm vs npm, Yarn, Bun
-
Recommended tools — pnpm as shop default