Skip to content
how-to confidence: high

npm Trusted Publishing Setup for First-Time Maintainers (with the GitHub Actions YAML That Works)

A first-publish guide for npm trusted publishing (OIDC) on GitHub Actions. The exact permissions block, the registry-url that matters, and the three lines that bypass your trusted publisher silently.

Published May 12, 2026 · kw: npm trusted publishing setup

Sources: S-001 S-002 S-003 S-005

You wired up an npm publish workflow, switched it from a long-lived NPM_TOKEN to OIDC trusted publishing, ran it, and the registry still asks for an auth token. This is the exact configuration that works in 2026, and the three things that quietly break it.

TLDR

To publish from GitHub Actions with npm trusted publishing:

  1. Configure the trusted publisher on npmjs.com for your package, pointing at your repo + workflow.
  2. Add permissions: { id-token: write, contents: read } to the publish job.
  3. Use actions/setup-node@v4 with registry-url: "https://registry.npmjs.org".
  4. Run npm publish --provenance --access public.
  5. Remove NPM_TOKEN from the workflow env — its presence makes npm prefer it over OIDC.

A copy-paste workflow is at the bottom.

Why the first publish breaks for most maintainers

Trusted publishing is two systems handshaking:

  • GitHub Actions mints an OIDC ID token claiming “this run is your-org/your-repo on branch main” — but only if you ask for it with permissions.id-token: write (see GitHub OIDC docs for npmS-002).
  • npm registry verifies that token, looks up the trusted publisher you configured at npmjs.com → your package → Settings → Trusted Publishers, and either accepts the publish or falls back to legacy auth (npm docsS-001).

Either side missing kills the handshake.

The recurring failure shape in npm/cli issues (S-005) is the workflow runs, the auth step “succeeds” because NPM_TOKEN is still in the env, and the package publishes — just without provenance, and without using the trusted publisher you set up.

Step-by-step fix

1. Configure the trusted publisher on npmjs.com

Sign in to https://www.npmjs.com. Open your package’s SettingsTrusted PublishersAdd a new trusted publisher. Pick GitHub Actions. Fill:

  • Organization or user: your GitHub owner.
  • Repository: your repo name.
  • Workflow filename: publish.yml (or whatever you named it — it must match exactly).
  • Environment name: optional. Leave blank unless you actually configured a GitHub Environment.

Save.

2. Set the workflow permissions

The job that publishes must grant id-token: write. Without this, GitHub never mints the OIDC token.

permissions:
  id-token: write
  contents: read

If you only set this at the workflow level, fine. If you set it at the job level, make sure the publish job has it.

3. Use setup-node v4 with the registry URL

actions/setup-node@v3 and earlier do not fully wire the OIDC audience for npm. Use v4.

- uses: actions/setup-node@v4
  with:
    node-version: 20
    registry-url: "https://registry.npmjs.org"

The registry-url line is load-bearing. Without it, npm’s CLI does not know which audience to request the OIDC token for, and the token it gets back is rejected by the registry.

4. Publish with --provenance

- run: npm publish --provenance --access public

--provenance generates the Sigstore attestation that proves “this tarball was built by this workflow on this commit” (Sigstore docsS-004). Without it your package shows no provenance badge on npm, even though the publish itself works. Provenance shipped in npm@9.5 (npm CLI release notesS-003); confirm node-version resolves to an npm version at or above that.

--access public matters only if your package is scoped (@you/lib). Scoped packages default to restricted access and a missing --access public is the most common reason a first publish appears to work but no one can npm install it.

5. Remove NPM_TOKEN

If NPM_TOKEN is set as an env or secrets.NPM_TOKEN is referenced anywhere in the job, npm’s CLI uses it instead of OIDC. The publish succeeds, but the trusted publisher you configured is bypassed and there is no provenance. Delete the secret reference. If you keep NPM_TOKEN around for migration reasons, scope it to a separate job that you do not run.

The workflow that works

name: Publish to npm

on:
  release:
    types: [published]

jobs:
  publish:
    name: npm publish
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - uses: actions/setup-node@v4
        with:
          node-version: 20
          registry-url: "https://registry.npmjs.org"

      - run: npm ci

      - run: npm test --if-present

      - run: npm publish --provenance --access public

Save as .github/workflows/publish.yml. The filename must match what you registered as the trusted publisher on npm.

Common mistakes (sourced)

  • permissions: id-token: write set at the wrong scope. If the publish step is in a reusable workflow called from another workflow, the inner one needs the permission too. (GitHub Actions OIDC docsS-002)
  • registry-url missing. Without it the OIDC audience is wrong and the token is rejected by npm. (npm docsS-001)
  • Stale package.json#repository.url. The repo URL in your package.json must match the GitHub repo running the workflow. If you forked and changed owner, update the URL. (npm/cli issuesS-005)
  • Scoped package, no --access public. Default is restricted; a first publish completes with no error but the package is private to your org.

FAQ

Do I need to delete NPM_TOKEN from npm before this works?

No — you can leave the npm token alive on npmjs.com as a fallback, but you must remove it from the workflow env, or npm CLI will prefer it.

Does this work for pnpm or yarn?

pnpm publish speaks OIDC starting at v9.5. yarn classic (1.x) does not. yarn modern (Berry) supports it through the npm publish plumbing. If you want trusted publishing, use npm publish or pnpm publish.

What if my repo has a monorepo with multiple packages?

You configure a trusted publisher per package on npm. The workflow can publish them all in one job; npm verifies the OIDC token per package.

Next step

Run the npm Trusted Publishing Preflight on your own package.json and workflow — it flags the four most common failure shapes above. For a deeper dive on the failures, read npm publish OIDC vs NPM_TOKEN and npm publish —provenance failed: 8 reasons.