release-npm.yml
Install dependencies, optionally build, and publish one or more packages to any NPM-compatible registry.
Supports Node.js and Bun runtimes, all major package managers (npm, yarn, pnpm, bun), and any registry — npmjs.org, GitHub Packages, or a custom host.
Inputs
| Input | Type | Description | Required | Default |
|---|---|---|---|---|
| RUNTIME | string | JavaScript runtime to use (node or bun) | No | node |
| RUNTIME_VERSION | string | Runtime version to use. Empty resolves to a per-runtime default (Node.js 24, Bun latest) | No | - |
| PACKAGE_MANAGER | string | Package manager to use (npm, yarn, pnpm, bun) | No | npm |
| WORKING_DIRECTORY | string | Working directory for install, build and publish commands | No | . |
| REGISTRY_URL | string | NPM registry URL | No | https://registry.npmjs.org |
| SCOPE | string | Scope for the NPM registry (e.g. @my-org). Leave empty for unscoped packages. | No | - |
| PRE_COMMAND | string | Shell command to run at repo root before install/build (e.g. build shared deps in a monorepo) | No | - |
| BUILD_COMMAND | string | Shell command to build the package (runs in WORKING_DIRECTORY) | No | - |
| PUBLISH_COMMAND | string | Custom publish command (overrides auto-detected publish). Runs in WORKING_DIRECTORY | No | - |
| TAG | string | NPM dist-tag for the published version (e.g. latest, beta, next) | No | latest |
| ACCESS | string | Package access level (public or restricted) | No | public |
| DRY_RUN | boolean | Perform a dry-run publish (validate without uploading) | No | false |
| FAIL_ON_ERROR | boolean | Whether to fail the workflow on publish errors | No | true |
| RUNS_ON | string | Runner labels as JSON array (e.g., '["ubuntu-24.04"]' or '["self-hosted", "linux"]') | No | ["ubuntu-24.04"] |
Secrets
| Secret | Description | Required |
|---|---|---|
| NPM_TOKEN | Authentication token for the NPM registry. Optional if the registry has trusted publishing (OIDC) configured for the calling workflow - see Trusted publishing below. | No |
Permissions
| Scope | Access | Description |
|---|---|---|
| contents | read | Check out the repository |
| id-token | write | Mint the OIDC token used for trusted publishing (npm, pnpm) |
| packages | write | Required only when publishing to GitHub Packages |
Notes
- Authentication uses the
NODE_AUTH_TOKENenvironment variable, set fromNPM_TOKEN, which is the standard mechanism understood by npm, yarn, pnpm and bun. - For the Node.js runtime,
actions/setup-nodecreates the.npmrcauth entry automatically based onREGISTRY_URLandSCOPE. - For the Bun runtime, the registry URL and auth token are written to the user-level
.npmrcmanually, sinceactions/setup-nodeis not invoked — this covers both the default registry and scoped registries. Bun has no OIDC/trusted-publishing support, soNPM_TOKENis required withRUNTIME: bun. - pnpm/yarn are set up automatically via Corepack when
PACKAGE_MANAGERispnpmoryarn; no manual install is needed, and the version pinned inpackage.json'spackageManagerfield (if any) is used. - The
yarnpublish step usesyarn npm publish(Yarn Berry / v2+). pnpm publishis called with--no-git-checksto avoid requiring a clean git state in CI.PRE_COMMANDruns at the repo root withNODE_AUTH_TOKENset — useful for building shared workspace packages before publishing.BUILD_COMMANDruns insideWORKING_DIRECTORY.PUBLISH_COMMANDfully overrides the auto-detected publish step and is the right option for Turborepo, Lerna, or any custom release tooling.DRY_RUNappends--dry-runto the publish command (all package managers support this flag) to validate packaging without uploading.FAIL_ON_ERROR: falsesetscontinue-on-error: trueon the publish step, useful when some packages in a matrix may already be published.- Dependency caches are keyed by package manager, OS, architecture, and the combined hash of all lock files.
Trusted publishing (OIDC)
npm and pnpm support trusted publishing: publishing via a short-lived OIDC token instead of a long-lived NPM_TOKEN. To use it:
- On npmjs.com, configure a trusted publisher for the package pointing at your repository and the entry-point workflow file that GitHub actually triggers (e.g.
.github/workflows/cd.yml) — not this reusablerelease-npm.ymlfile. npm validates the caller workflow that started the run, not any reusable workflow it calls into. - Grant
id-token: writeon the calling job (in yourcd.yml) — this workflow already requests it internally, but both are required since permissions must be explicit at every level of a reusable-workflow call chain. - Omit the
NPM_TOKENsecret, or keep passing it as a fallback — npm's CLI (≥ 11.5.1, bundled with Node.js 24+) tries OIDC first and only falls back to a static token if OIDC isn't available. - pnpm's OIDC support is newer and has had regressions in some releases (see pnpm#11513) — pin a known-good
packageManagerversion and verify a real publish before relying on it exclusively.
If you switch an existing package from a token-based setup to trusted publishing, remember to update the trusted publisher's registered workflow path whenever you change which file is the actual entry point (e.g. after moving CI/CD to reusable workflows).
Examples
Simple publish to npmjs.org
Installs deps with npm and publishes the package at the repo root.
jobs:
release-npm:
uses: this-is-tobi/github-workflows/.github/workflows/release-npm.yml@v0
permissions:
contents: read
id-token: write
with:
WORKING_DIRECTORY: .
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}Trusted publishing (no token)
Once a trusted publisher is configured on npmjs.com for this exact repo and calling workflow file (see Trusted publishing), the NPM_TOKEN secret can be dropped entirely — id-token: write is all that's needed.
jobs:
release-npm:
uses: this-is-tobi/github-workflows/.github/workflows/release-npm.yml@v0
permissions:
contents: read
id-token: write
with:
WORKING_DIRECTORY: .Monorepo: build shared deps then publish
Builds a shared workspace package first (PRE_COMMAND at repo root), then installs and builds the target package before publishing.
jobs:
release-npm:
uses: this-is-tobi/github-workflows/.github/workflows/release-npm.yml@v0
permissions:
contents: read
id-token: write
with:
RUNTIME: bun
RUNTIME_VERSION: "1.3.10"
PACKAGE_MANAGER: bun
PRE_COMMAND: "bun run build --filter=@my-org/shared"
BUILD_COMMAND: "bun run build"
WORKING_DIRECTORY: ./packages/my-lib
TAG: latest
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}Publish to GitHub Packages
Set REGISTRY_URL to the GitHub Packages endpoint and pass github.token as the secret. The packages: write permission is required on the calling job.
jobs:
release-npm:
uses: this-is-tobi/github-workflows/.github/workflows/release-npm.yml@v0
permissions:
contents: read
packages: write
with:
REGISTRY_URL: "https://npm.pkg.github.com"
SCOPE: "@my-org"
TAG: latest
secrets:
NPM_TOKEN: ${{ github.token }}Prerelease with dry-run validation
Publishes under the beta dist-tag. DRY_RUN: true validates packaging without uploading — useful to check before the actual release.
jobs:
release-npm:
uses: this-is-tobi/github-workflows/.github/workflows/release-npm.yml@v0
permissions:
contents: read
id-token: write
with:
TAG: beta
DRY_RUN: true
ACCESS: public
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}Custom publish command (Turborepo / Lerna)
When a custom release tool manages publishing, override the default step entirely via PUBLISH_COMMAND.
jobs:
release-npm:
uses: this-is-tobi/github-workflows/.github/workflows/release-npm.yml@v0
permissions:
contents: read
id-token: write
with:
PUBLISH_COMMAND: "npx turbo publish --filter=./packages/*"
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}