Skip to content

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

InputTypeDescriptionRequiredDefault
RUNTIMEstringJavaScript runtime to use (node or bun)Nonode
RUNTIME_VERSIONstringRuntime version to use. Empty resolves to a per-runtime default (Node.js 24, Bun latest)No-
PACKAGE_MANAGERstringPackage manager to use (npm, yarn, pnpm, bun)Nonpm
WORKING_DIRECTORYstringWorking directory for install, build and publish commandsNo.
REGISTRY_URLstringNPM registry URLNohttps://registry.npmjs.org
SCOPEstringScope for the NPM registry (e.g. @my-org). Leave empty for unscoped packages.No-
PRE_COMMANDstringShell command to run at repo root before install/build (e.g. build shared deps in a monorepo)No-
BUILD_COMMANDstringShell command to build the package (runs in WORKING_DIRECTORY)No-
PUBLISH_COMMANDstringCustom publish command (overrides auto-detected publish). Runs in WORKING_DIRECTORYNo-
TAGstringNPM dist-tag for the published version (e.g. latest, beta, next)Nolatest
ACCESSstringPackage access level (public or restricted)Nopublic
DRY_RUNbooleanPerform a dry-run publish (validate without uploading)Nofalse
FAIL_ON_ERRORbooleanWhether to fail the workflow on publish errorsNotrue
RUNS_ONstringRunner labels as JSON array (e.g., '["ubuntu-24.04"]' or '["self-hosted", "linux"]')No["ubuntu-24.04"]

Secrets

SecretDescriptionRequired
NPM_TOKENAuthentication token for the NPM registry. Optional if the registry has trusted publishing (OIDC) configured for the calling workflow - see Trusted publishing below.No

Permissions

ScopeAccessDescription
contentsreadCheck out the repository
id-tokenwriteMint the OIDC token used for trusted publishing (npm, pnpm)
packageswriteRequired only when publishing to GitHub Packages

Notes

  • Authentication uses the NODE_AUTH_TOKEN environment variable, set from NPM_TOKEN, which is the standard mechanism understood by npm, yarn, pnpm and bun.
  • For the Node.js runtime, actions/setup-node creates the .npmrc auth entry automatically based on REGISTRY_URL and SCOPE.
  • For the Bun runtime, the registry URL and auth token are written to the user-level .npmrc manually, since actions/setup-node is not invoked — this covers both the default registry and scoped registries. Bun has no OIDC/trusted-publishing support, so NPM_TOKEN is required with RUNTIME: bun.
  • pnpm/yarn are set up automatically via Corepack when PACKAGE_MANAGER is pnpm or yarn; no manual install is needed, and the version pinned in package.json's packageManager field (if any) is used.
  • The yarn publish step uses yarn npm publish (Yarn Berry / v2+).
  • pnpm publish is called with --no-git-checks to avoid requiring a clean git state in CI.
  • PRE_COMMAND runs at the repo root with NODE_AUTH_TOKEN set — useful for building shared workspace packages before publishing. BUILD_COMMAND runs inside WORKING_DIRECTORY.
  • PUBLISH_COMMAND fully overrides the auto-detected publish step and is the right option for Turborepo, Lerna, or any custom release tooling.
  • DRY_RUN appends --dry-run to the publish command (all package managers support this flag) to validate packaging without uploading.
  • FAIL_ON_ERROR: false sets continue-on-error: true on 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:

  1. 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 reusable release-npm.yml file. npm validates the caller workflow that started the run, not any reusable workflow it calls into.
  2. Grant id-token: write on the calling job (in your cd.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.
  3. Omit the NPM_TOKEN secret, 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.
  4. pnpm's OIDC support is newer and has had regressions in some releases (see pnpm#11513) — pin a known-good packageManager version 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.

yaml
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.

yaml
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.

yaml
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.

yaml
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.

yaml
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.

yaml
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 }}