npm Trusted Publishing (OIDC)
Configure and debug secure, tokenless npm publishing from GitHub Actions using OpenID Connect (OIDC) and Provenance.
Core Requirements
Trusted Publishing eliminates the need for long-lived NPM_TOKEN secrets by using short-lived, cryptographically-signed tokens.
1. GitHub Actions Permissions (CRITICAL)
The workflow MUST grant id-token: write permissions to fetch the OIDC token. This can be declared globally at the top level of the file or inside the target job block.
permissions:
id-token: write # Required for OIDC provenance authentication
contents: read # Required for repository checkout
2. Node.js Version
Trusted Publishing with provenance requires a modern Node.js environment (v20+) and npm v9.5.0+. Using standard runner defaults or .nvmrc configurations works reliably.
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: lts/* # npm v11.5.1+ (Node v24+) is required for Trusted Publishing
registry-url: 'https://registry.npmjs.org'
3. package.json Metadata
NPM validates provenance against the repository field.
.gitSuffix &git+Prefix are OPTIONAL: You can use the raw HTTPS URL (e.g.https://github.com/USER/REPO) or the git-specific URL (e.g.git+https://github.com/USER/REPO.git). Most modern npm/node setups handle these automatically. However, OIDC matching can sometimes be sensitive to normalization; if OIDC fails, try toggling these formats.
{
"repository": {
"type": "git",
"url": "git+https://github.com/USER/REPO.git"
}
}
Workflow Implementation
A standard reference template is provided inside this skill folder at references/publish.yml for drop-in integration.
Recommended Publish Step
Use standard publish flags to enable provenance.
- name: Publish to npm
run: npm publish --provenance --access public --registry https://registry.npmjs.org/
Troubleshooting Guide
400 Bad Request - OIDC publish authorize: Invalid token
- Cause 1 (Version Shadowing): If a manual or test version of the package was published previously, automated GitHub Actions deployment runs attempting to verify token permissions against an existing version string are rejected on registry pre-authorization checks to prevent shadow collisions.
- Fix 1: Ensure the
versionproperty inpackage.jsonis strictly bumped to a new, unreleased semver identifier prior to workflow execution. - Cause 2 (Manual Triggers): Manually triggering a publish run via
workflow_dispatch(rather than an authentic git push) generates an OIDC token payload whererefandshaenvironment claims diverge from standard annotated publication tags. The npm pre-authorization gateway routinely drops these customized dynamic event scopes. - Fix 2: To guarantee unblocked authorization passes on npm servers, trigger the publication workflow strictly by pushing an authentic annotated Git release tag (e.g.,
git tag v1.0.0 && git push origin v1.0.0).
404 Not Found
- Cause 1 (First Publish of a New Package): npm does not support "Pending Publishers" (unlike PyPI). You cannot configure Trusted Publishing for a package that does not exist yet. Attempting to publish a new package via OIDC first will fail with a 404 (e.g.,
'packagename@1.4.12' is not in this registry) because npm cannot authorize the publish without a pre-configured relationship. - Fix 1:
- Perform the initial publish manually from your local machine using standard token/session authentication (
npm loginthennpm publish --access public). - Once the package is created on npmjs.com, go to Package Settings -> Trusted Publishing and configure the GitHub Actions mapping.
- Future publishes can then run automatically via the OIDC workflow.
- Perform the initial publish manually from your local machine using standard token/session authentication (
- Cause 2 (Registry Mismatch): The publish command is attempting to hit a private registry or mirror where the package does not exist or cannot be written.
- Fix 2: Explicitly set
--registry https://registry.npmjs.org/in the publish command. Ensure no.npmrcis overriding the registry to a mirror or private proxy. - Cause 3 (Misconfigured or Missing Trusted Publisher on Existing Package): npm may return
404 Not Found(instead of403 Forbidden) when the OIDC token claims do not match any configured Trusted Publisher for the package, or if Trusted Publishing has not been configured yet for this package. - Fix 3:
- Log in to npmjs.com, go to the package settings, and select Publishing (or Trusted Publishers).
- Verify that the configured GitHub Organization/User matches the repository owner (e.g.,
so-fancyforso-fancy/diff-so-fancy) and is case-sensitive. - Verify that the Repository and Workflow filename (
publish.yml) match exactly. - Ensure the Environment field on npm is empty if it is not explicitly defined in the GitHub Actions workflow YAML.
- Cause 4 (repository.url format sensitivity): The npm registry's OIDC validation is sensitive to the
repository.urlformat inpackage.json. In some cases, thegit+prefix or missing.gitextension can cause validation mismatches, even if the npm CLI claims to auto-correct/normalize it. - Fix 4:
- Ensure the URL matches your GitHub repo.
- Try explicitly setting it to
git+https://github.com/owner/repo.git(official format) to avoid normalization warnings. - If that fails, try using
https://github.com/owner/repo.git(withoutgit+but with.git) as some users have reported this resolves OIDC claim mismatches.
422 Unprocessable Entity
- Cause: Metadata mismatch (most common:
repository.urlinpackage.jsonis missing or doesn't match the GitHub URL). - Fix: Validate
package.jsonagainst the actual repository URL.
403 Forbidden / Unauthorized
- Cause: The "Trusted Publisher" is not configured on
npmjs.comor has an "Environment" mismatch. - Fix:
- Check npmjs.com → Package Settings → Trusted Publishing.
- Verify Organization, Repository, and Workflow filename (e.g.,
publish.yml) are exact. - If an Environment is specified on npm (e.g., "release"), it MUST be specified in the workflow:
jobs.publish.environment: release.
Validation Tool
The skill includes a validation script to check your local configuration:
node <path-to-skill>/scripts/validate.mjs <path-to-package.json>
It verifies:
- Mandatory
.gitsuffix onrepository.url. - Presence of
id-token: writepermission. - Absence of conflicting
registry-urlconfigurations insetup-node. - Modern Node version (
lts/*or24+) for robust OIDC support.
User Instructions
When setting up a new project, provide these instructions to the user in strict sequential order:
- Commit Workflow First (CRITICAL Order of Operations): You MUST commit and push the
.github/workflows/publish.ymlfile to your remote GitHub repository before initializing the trusted publisher mapping on npmjs.com. Configuring the dashboard mapping while the upstream repository lacks the workflow script can cause preliminary token evaluation handshakes to cache invalid target structures, resulting in unresolvable400 Bad Requestauthorization drops during deployment runs. - Initial Publish (For New Packages): If this is the first time publishing this package, you cannot use Trusted Publishing immediately. You must first publish the package manually from your local terminal to create it on the registry:
npm login npm publish --access public - Configure npmjs.com: Once the package exists on npm, go to the package page on npmjs.com -> Settings -> Trusted Publishing and add a new "GitHub Actions" publisher.
- Fields:
- Organization:
YOUR_USERNAME(or your npm organization name) - Repository:
YOUR_REPO - Workflow filename:
publish.yml - Environment: Leave blank (unless explicitly requested).
- Organization:
- No Secrets Needed: Remind the user that
NPM_TOKENis NO LONGER REQUIRED in GitHub Secrets.
Additional Resources
For more detailed information, consult the official documentation. It is often helpful to fetch this URL to verify the latest field requirements or troubleshooting steps: https://docs.npmjs.org/trusted-publishers