technical 8 min read

npm Trusted Publishing with GitHub Actions: Publish Packages Without NPM_TOKEN

A practical guide to publishing npm packages from GitHub Actions with Trusted Publishing, OIDC, and provenance instead of long-lived NPM_TOKEN secrets.

This guide explains how to publish npm packages from GitHub Actions with npm Trusted Publishing, OpenID Connect (OIDC), and npm provenance, without storing a long-lived NPM_TOKEN in GitHub Secrets.

If you are debugging npm publish E404 Not Found, Access token expired or revoked, expired npm tokens, failed GitHub Actions releases, scoped package 404 errors, or npm publish --access public behavior, use the configuration and checklist below.

Key conclusion

When publishing npm packages from GitHub Actions, prefer npm Trusted Publishing over storing a long-lived NPM_TOKEN.

A correct setup requires four things:

  1. Add a Trusted Publisher in the npm package settings, with the exact GitHub organization or user, repository, and workflow filename.
  2. Use a GitHub-hosted runner. Self-hosted runners are not supported for npm Trusted Publishing.
  3. Add permissions: id-token: write to the workflow so GitHub Actions can issue an OIDC identity token.
  4. Use a recent Node/npm toolchain that supports the OIDC publishing flow. Node 24 plus npm latest is a practical baseline.

This article is based on a real migration of n2n-memory from token-based publishing to Trusted Publishing. The technical configuration shown here has been verified.

When this guide applies

Use this guide if you are searching for:

  • How to configure npm Trusted Publishing
  • How to publish an npm package from GitHub Actions
  • How to publish an npm package without NPM_TOKEN
  • npm OIDC publishing setup
  • How to fix npm publish E404 Not Found - PUT
  • How to fix Access token expired or revoked
  • Why GitHub Actions fails after an npm token expires
  • How to enable npm provenance
  • Why scoped package publishing returns 404
  • When npm publish --access public is required

Why replace NPM_TOKEN with Trusted Publishing

The traditional setup stores NPM_TOKEN in GitHub Secrets, sets NODE_AUTH_TOKEN in CI, and runs npm publish. It works, but it has operational and security problems:

  • Tokens expire, get revoked, or need manual rotation.
  • A leaked token can be difficult to scope and contain.
  • Two-factor authentication can require bypass or automation-token handling.
  • Managing secrets across multiple repositories and packages becomes messy.

Trusted Publishing works differently. npm trusts a specific workflow in a specific GitHub repository. When that workflow runs npm publish, GitHub Actions provides a short-lived OIDC credential. npm verifies the workflow identity, allows the publish, and can generate provenance metadata.

In short: Trusted Publishing makes npm trust the GitHub workflow that is publishing, instead of trusting a long-lived token.

npm setup: add a Trusted Publisher

Open the package access page:

https://www.npmjs.com/package/<package-name>/access

For example:

https://www.npmjs.com/package/n2n-memory/access

Go to Settings, find Trusted Publishers, choose GitHub Actions, and fill in:

Field Value
Organization or user n2ns, the GitHub account or organization
Repository n2n-memory, the GitHub repository name
Workflow filename publish.yml, filename only
Environment name Leave blank unless you use a GitHub Environment

Important details:

  • Workflow filename must be only the filename, such as publish.yml.
  • Do not enter the full path .github/workflows/publish.yml.
  • Organization or user, Repository, and Workflow filename must exactly match GitHub.
  • These fields are case-sensitive.
  • Click Save changes after editing, otherwise the configuration is not applied.

This .github/workflows/publish.yml is a verified baseline:

name: Publish Package on: push: tags: - 'v*' workflow_dispatch: permissions: id-token: write contents: read jobs: publish: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '24' registry-url: 'https://registry.npmjs.org' - name: Upgrade npm run: npm install -g npm@latest - run: npm ci - run: npm run check - run: npm publish --access public

Key points:

  • permissions: id-token: write is required for OIDC publishing.
  • runs-on: ubuntu-latest uses a GitHub-hosted runner. npm Trusted Publishing does not support self-hosted runners.
  • node-version: '24' avoids incomplete support in older npm CLI versions.
  • npm install -g npm@latest ensures the npm CLI is recent enough.
  • registry-url should explicitly point to https://registry.npmjs.org.
  • Public scoped packages should use:
npm publish --access public
  • Do not set NODE_AUTH_TOKEN for npm publish, or npm may use the legacy token path instead of OIDC Trusted Publishing.

Keep the real repository URL and public publishing configuration in package.json:

{ "name": "n2n-memory", "version": "1.2.2", "repository": { "type": "git", "url": "git+https://github.com/n2ns/n2n-memory.git" }, "publishConfig": { "access": "public", "provenance": true } }

Notes:

  • repository.url should point to the real GitHub repository.
  • npm provenance checks the publishing source. A mismatched repository can cause failures or incorrect provenance metadata.
  • Public scoped packages should keep publishConfig.access: "public".
  • publishConfig.provenance: true explicitly enables npm provenance.

Full release flow

After configuration:

  1. Add and save the Trusted Publisher in npm package settings.
  2. Commit and push .github/workflows/publish.yml:
git add .github/workflows/publish.yml package.json git commit -m "chore: configure npm trusted publishing" git push
  1. Confirm the package.json version has not been published before.
  2. Create and push a tag:
git tag v1.2.2 git push origin v1.2.2
  1. Wait for the Publish Package job in GitHub Actions.
  2. Verify the npm release:
npm view n2n-memory dist-tags.latest npm view n2n-memory versions --json

If dist-tags.latest points to the new version, the publish succeeded.

Troubleshooting

Why does npm publish return E404 Not Found?

A typical error looks like this:

npm error code E404 npm error 404 Not Found - PUT https://registry.npmjs.org/@scope%2fpackage - Not found npm error 404 '@scope/[email protected]' is not in this registry.

For npm publish, this often means the package does not have the right publishing permission, or the Trusted Publisher identity does not match. Check in order:

  • The npm Trusted Publisher was saved with Save changes
  • Organization or user exactly matches the GitHub owner
  • Repository exactly matches the GitHub repository name
  • Workflow filename is only publish.yml, without a path
  • The workflow file is actually .github/workflows/publish.yml
  • The workflow uses a GitHub-hosted runner
  • The workflow includes permissions: id-token: write
  • Node is 24 and npm is upgraded to latest
  • The public scoped package is published with npm publish --access public

How do I fix Access token expired or revoked?

If you still see this after migrating to Trusted Publishing:

Access token expired or revoked

npm CLI is probably still using the old token authentication path. Check:

  • Remove NODE_AUTH_TOKEN from the npm publish step.
  • Remove token-based .npmrc writes from the workflow.
  • Upgrade Node/npm.
  • Stop using old publish actions or scripts that inject a token.

With Trusted Publishing configured correctly, the release workflow does not need NPM_TOKEN.

Why does npm whoami not validate OIDC?

npm whoami uses traditional authentication. It does not prove that Trusted Publishing OIDC is configured correctly.

The reliable validation is to run the publish workflow and confirm that npm publish succeeds.

Why can workflow_dispatch fail?

npm checks the identity of the workflow that actually publishes. Keep npm publish inside the same publish.yml that is registered as the Trusted Publisher. Avoid publishing through nested or reusable workflows unless you have verified that the registered workflow identity still matches.

Why can publishing fail after transferring package ownership?

Transferring npm package ownership does not automatically update Trusted Publisher settings. After a transfer, verify:

  • The current npm account has write access to the package.
  • The Trusted Publisher points to the current GitHub owner and repository.
  • The workflow filename matches npm settings.
  • package.json has the current repository.url.

Minimal checklist

Before publishing, confirm:

  • npm package has a saved Trusted Publisher
  • Workflow filename is publish.yml, filename only
  • Workflow path is .github/workflows/publish.yml
  • Workflow includes permissions: id-token: write
  • Workflow uses a GitHub-hosted runner such as ubuntu-latest
  • Workflow uses node-version: '24'
  • Workflow runs npm install -g npm@latest
  • npm publish does not use NODE_AUTH_TOKEN
  • package.json repository.url points to the current GitHub repository
  • package.json includes publishConfig.access: "public"
  • The package version has not been published before

FAQ

Does npm Trusted Publishing still need NPM_TOKEN?

No. Trusted Publishing uses GitHub Actions OIDC short-lived identity instead of a long-lived NPM_TOKEN.

Does npm Trusted Publishing support self-hosted runners?

No. The publishing workflow must use a GitHub-hosted runner, such as ubuntu-latest.

What should Workflow filename be?

Use only the filename, such as publish.yml. Do not use .github/workflows/publish.yml.

Why use --access public for scoped packages?

Public scoped packages can otherwise be treated as private. Use:

npm publish --access public

Also keep this in package.json:

"publishConfig": { "access": "public", "provenance": true }

Are Trusted Publishing and provenance the same thing?

No. Trusted Publishing validates who is publishing through GitHub Actions OIDC. Provenance records where the package came from. They are different features that work well together.

References

We use cookies

We use cookies to enhance your browsing experience, analyze site traffic, and personalize content. By clicking "Accept", you consent to our use of cookies. Learn more