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

pnpm

The package manager binary (installed via Corepack or otherwise on your PATH).

Global flags (optional)

Options that apply before the subcommand — e.g. --filter, -C, -r, --workspace-root. They change which package pnpm treats as the context.

run

Subcommand: execute a named script from a package.json scripts block. For common script names, run is optional (pnpm devpnpm run dev).

<script-name>

A key under "scripts" in the target package’s package.json — e.g. "dev", "build", "lint". Not invented by pnpm; defined by the project (or a tool template).

-- script-args

Arguments passed through to the underlying command (everything after -- goes to the script, not to pnpm).

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:

  1. Finds the target package (see [root-first] below).

  2. Looks up "dev" in that package’s "scripts".

  3. Runs the associated shell command (next dev --turbopack here) 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.jsonscripts.

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.json may have no dev script (only deploy helpers, formatters, etc.).

  • The app you care about (site-nextjs, apps/web, …) defines dev in its package.json.

  • Running bare pnpm dev at the root fails if root scripts has no dev key — 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 target package.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 dev for 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

pnpm -F site-nextjs dev

pnpm -C site-nextjs dev

Filter graph suffixes (optional)

Pattern Effect

--filter site-nextjs

That package only.

--filter site-nextjs…​

Package and its workspace dependencies (build order helpers).

--filter …​site-nextjs

Package and workspace dependents (things that depend on it).

--filter "./packages/*"

All packages matching the path glob.

Recursive -r

pnpm -r dev runs dev in every workspace package that defines it. Useful for “start all apps,” noisy when only one package has dev. Combine with filter to limit scope:

pnpm -r --filter site-nextjs dev

Recipes (stay at repo root)

Goal Command (from repo root)

Install entire workspace

pnpm install

Dev server for one app

pnpm --filter site-nextjs dev or pnpm -C site-nextjs dev

Build one app

pnpm --filter site-nextjs build

Run a bin from a package (e.g. Convex CLI)

pnpm --filter site-nextjs exec convex dev

Lint only the app package

pnpm --filter site-nextjs lint

Run root-only script (if defined)

pnpm run vercel:deploy (no filter — root is already the default cwd)

Epistemology checklist (how to read a tutorial command)

When a blog post throws pnpm run dev at you, ask:

  1. Which package? Root or nested? In a monorepo, dev may not live at root.

  2. Where is the script defined? Open that package’s package.json"scripts".

  3. What does the script actually run? The value is the real command (next dev, vite, tsx watch, …).

  4. Where should I stand? Repo root + --filter / -C, or genuinely single-package root.

  5. Is run required? No for standard script names; pnpm dev and pnpm run dev are the same.

Command Role

pnpm exec <bin>

Run a dependency binary from the target package’s node_modules/.bin (e.g. pnpm exec convex dev).

pnpm dlx <pkg>

One-off download + run (like npx); not a project script.

pnpm install

Always workspace-wide from repo root in monorepos; do not cd into an app to install unless you know why.