Install pnpm and a JavaScript runtime (Node.js, Bun, or Deno) in a single GitHub Actions step.
pnpm ships a self-contained release binary — the action downloads it for the runner's platform from the npm registry, refusing anything whose npm signature or checksum does not check out (no Node.js or npm needed) and then uses pnpm runtime set to install the requested runtime. The runtime binary is placed on PATH for subsequent steps, replacing the need for actions/setup-node, oven-sh/setup-bun, or denoland/setup-deno. pnpm install runs automatically when a package.json is present.
Note
pnpm/setup@v2 installs pnpm v11 and newer only — it relies on pnpm's self-contained release binaries and the pnpm runtime command, both available from v11. v1 installed pnpm through npm and could set up pnpm 10; if you need pnpm 10 or older, use pnpm/action-setup instead.
One caveat: pnpm v11 publishes no binary for Intel macOS (darwin-x64); use v12 or newer on Intel macOS runners.
If your package.json declares devEngines.runtime, the action picks up the runtime and version from there automatically — no inputs required.
| Name | Description |
|---|---|
version |
Version of pnpm to install: an exact version, a semver range (^12.0.0), or a dist-tag (next-12). Must resolve to v11 or newer. Optional when packageManager or devEngines.packageManager is set in package.json. |
dest |
Where to store pnpm files. Defaults to ~/setup-pnpm. |
runtime |
Runtime spec, in <name> or <name>@<version> form (e.g. node@22, node@lts, bun@latest, deno@2). Supported names: node, bun, deno. When the version is omitted, falls back to devEngines.runtime in package.json, then to lts (for node) / latest. If the input itself is omitted, the action reads devEngines.runtime from package.json. |
cache |
Cache the pnpm store directory. Default: false. |
cache-dependency-path |
Path(s) to the pnpm lockfile, used to compute the cache key. Default: pnpm-lock.yaml. |
package-json-file |
Path to package.json (relative to GITHUB_WORKSPACE). Default: package.json. |
install |
Run pnpm install after setup. Default: true. Set to false for jobs that only need pnpm itself (e.g. pnpm audit, lockfile-only regeneration). |
token |
No longer used. pnpm is fetched from the npm registry and verified against npm's signature, so the action makes no GitHub API request. Kept so workflows that pass it keep working. |
| Name | Description |
|---|---|
dest |
Expanded path of dest. |
bin-dest |
Directory containing the pnpm / pnpx binaries. |
runtime-name |
Name of the installed runtime, or empty string if none was installed. |
runtime-version |
Resolved version of the installed runtime, or empty string if none was installed. |
// package.json
{
"packageManager": "pnpm@12.0.0-beta.4",
"devEngines": {
"runtime": { "name": "node", "version": "^22.0.0", "onFail": "download" }
}
}jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: pnpm/setup@v2
- run: node --version
- run: pnpm testpnpm install runs automatically because the workspace has a package.json.
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node: [22, 24, 26]
steps:
- uses: actions/checkout@v7
- uses: pnpm/setup@v2
with:
runtime: node@${{ matrix.node }}
- run: pnpm test- uses: pnpm/setup@v2
with:
runtime: bun@latest
- uses: pnpm/setup@v2
with:
runtime: deno@2- uses: pnpm/setup@v2
with:
cache: trueFor jobs that only need pnpm itself — e.g. pnpm audit, lockfile-only regeneration — set install: false:
- uses: pnpm/setup@v2
with:
install: false
- run: pnpm audit- The action resolves the requested version (exact, range, or dist-tag) against the npm registry, then downloads the matching self-contained release archive for the runner's platform (
pnpm-<os>-<arch>.tar.gz, orpnpm-win32-<arch>.zipon Windows) from pnpm's GitHub releases. It verifies the archive against the SHA-256 digest GitHub publishes for the asset, extracts thepnpmexecutable (and, for pnpm builds that need it, its bundleddist/), and links thepnpx,pn, andpnxaliases intodest. No Node.js or npm is involved. PNPM_HOMEis exported anddestplus$PNPM_HOME/binare added toPATH.- The action runs
pnpm runtime set <name> <version> -g, which downloads the requested runtime into$PNPM_HOME/bin— makingnode,bun, ordenoavailable to later workflow steps. It then exportsPNPM_CONFIG_GLOBAL_SHIMS={"<name>":false}so that runtime stays the one later steps get; see Context-aware global shims. - If a
package.jsonexists in the workspace, the action runspnpm install(unlessinstall: falseis set). When theruntimeinput is set,--no-runtimeis appended so the just-installed runtime isn't shadowed by a different version declared indevEngines.runtime.
pnpm 12 links global runtime bins as context-aware shims: running node inside a project switches to the version that project pins in devEngines.runtime, fetching it on demand. In a workflow that is rarely what you want — a matrix job asking for node@22 would run the repository's pinned version instead, and even when the two versions agree pnpm materializes a second copy outside $PNPM_HOME.
So whenever the action installs a runtime, it exports PNPM_CONFIG_GLOBAL_SHIMS with that runtime disabled ({"node":false}), leaving every other runtime at pnpm's defaults. To keep the switching behaviour, set the variable yourself — the action never overwrites a value the workflow already provides:
- uses: pnpm/setup@v2
env:
PNPM_CONFIG_GLOBAL_SHIMS: '{"node":"auto"}'
with:
runtime: node@22