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:
- Add a Trusted Publisher in the npm package settings, with the exact GitHub organization or user, repository, and workflow filename.
- Use a GitHub-hosted runner. Self-hosted runners are not supported for npm Trusted Publishing.
- Add
permissions: id-token: writeto the workflow so GitHub Actions can issue an OIDC identity token. - 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 publicis 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 filenamemust be only the filename, such aspublish.yml.- Do not enter the full path
.github/workflows/publish.yml. Organization or user,Repository, andWorkflow filenamemust exactly match GitHub.- These fields are case-sensitive.
- Click Save changes after editing, otherwise the configuration is not applied.
Recommended GitHub Actions workflow
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: writeis required for OIDC publishing.runs-on: ubuntu-latestuses 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@latestensures the npm CLI is recent enough.registry-urlshould explicitly point tohttps://registry.npmjs.org.- Public scoped packages should use:
npm publish --access public
- Do not set
NODE_AUTH_TOKENfornpm publish, or npm may use the legacy token path instead of OIDC Trusted Publishing.
Recommended package.json configuration
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.urlshould 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: trueexplicitly enables npm provenance.
Full release flow
After configuration:
- Add and save the Trusted Publisher in npm package settings.
- 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
- Confirm the
package.jsonversion has not been published before. - Create and push a tag:
git tag v1.2.2
git push origin v1.2.2
- Wait for the
Publish Packagejob in GitHub Actions. - 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 userexactly matches the GitHub owner -
Repositoryexactly matches the GitHub repository name -
Workflow filenameis onlypublish.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_TOKENfrom thenpm publishstep. - Remove token-based
.npmrcwrites 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.jsonhas the currentrepository.url.
Minimal checklist
Before publishing, confirm:
- npm package has a saved Trusted Publisher
-
Workflow filenameispublish.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 publishdoes not useNODE_AUTH_TOKEN -
package.jsonrepository.urlpoints to the current GitHub repository -
package.jsonincludespublishConfig.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.