Fixing npm publish 403 Forbidden Errors
A 403 Forbidden from npm publish means the registry accepted your request, recognized the package, and then refused the write. Unlike a 401 (you are not authenticated at all), a 403 is an authorization failure: the token is valid but lacks permission, the version already exists, the name is taken, or the access level is wrong. This page maps each verbatim error string to its root cause and the exact command that fixes it.
Exact Symptoms
The most common 403 strings, copied verbatim from npm's output:
npm error code E403
npm error 403 Forbidden - PUT https://registry.npmjs.org/@acme%2fwidget
npm error You do not have permission to publish "@acme/widget". Are you logged in as the correct user?
npm error code E403
npm error 403 Forbidden - PUT https://registry.npmjs.org/widget
npm error Package name too similar to existing package widget-js; try renaming your package
npm error code E403
npm error 403 Forbidden - PUT https://registry.npmjs.org/@acme%2fwidget
npm error cannot publish over previously published version 2.1.0.
npm error code E403
npm error 403 Forbidden - PUT https://registry.npmjs.org/widget
npm error You cannot publish over the previously published versions: 1.0.0.
Root-Cause Analysis
A 403 narrows to one of five causes. Identify yours from the message above, then jump to the matching resolution.
Cause 1 — Version already exists
cannot publish over previously published version means the version in package.json is already on the registry. npm versions are immutable; you cannot overwrite one. Bump it.
Cause 2 — Token scope or expiry
You do not have permission to publish with a granular token usually means the token is not scoped to this package or organization, has expired, or was created read-only. The publish lifecycle and token model are covered in npm Registry Publishing Workflows.
Cause 3 — 2FA / missing OTP
If your account requires two-factor authentication at the auth-and-writes level and you publish interactively without an OTP, the write is forbidden. Supply --otp or use an automation token configured to bypass 2FA in CI.
Cause 4 — Restricted access on a scoped package
A scoped package defaults to restricted; a public publish without --access public is forbidden (often surfacing as 402, sometimes 403 depending on plan). This case is dissected in Publishing Scoped Packages to npm.
Cause 5 — Name taken or too similar
For unscoped names, too similar to existing package or an outright ownership conflict means the name is unavailable. Rename, or move under a scope you control.
Resolution Steps
- Confirm who you are authenticated as:
If this errors, you have anpm whoami401, not a403— re-authenticate first. - Check whether the version already exists:
If your target version is listed, bump it:npm view @acme/widget versions --jsonnpm version patch # or minor / major - Verify your token can write to this package:
If the token is wrong-scoped or expired, mint a new granular token scoped to the package or organization and update the CI secret.npm access list packages # confirm read-write for the scope/package - Supply an OTP for interactive 2FA publishes:
npm publish --otp=123456 - Set public access for a scoped package:
npm publish --access public - Rename if the name is taken by moving under a scope you own:
{ "name": "@yourscope/widget" }
Validation
# Re-run as a simulation; a clean dry run means the write should now succeed
npm publish --dry-run --access public
# After a real publish, confirm the new version landed
npm view @acme/widget version
Prevention
- Run
npm publish --dry-runin CI on every pull request to surface access and permission problems before a release tag fires. - Use granular access tokens scoped to the exact package or organization, with an expiry, so a wrong-scope 403 is caught at token-creation time.
- Let your version-bump tooling own the version field so a human never re-publishes an existing version; release automation lives under Package Publishing & Release Engineering.
- Pin
publishConfig.accessin the manifest so the access level cannot drift between machines.
Frequently Asked Questions
What is the difference between a 401 and a 403 from npm publish?
A 401 means you are not authenticated — the registry does not know who you are. A 403 means you are authenticated but the action is not allowed: the token lacks permission, the version already exists, 2FA is unsatisfied, or the access level is wrong. Run npm whoami to tell them apart.
Why do I get "cannot publish over previously published version"?
The version in package.json is already on the registry, and npm versions are immutable. Bump the version with npm version patch (or minor/major) and publish again. You cannot overwrite or delete-and-reuse a published version number.
My CI token worked yesterday and now returns 403 — what changed?
The most likely causes are an expired granular token, a token whose scope no longer covers a newly added package, or an account that was moved to stricter 2FA. Regenerate a granular token scoped to the package or organization, confirm with npm access list packages, and update the CI secret.
Related
- Publishing Scoped Packages to npm — the access-level cause of 402/403 failures on
@scope/name. - Setting Up npm Provenance with GitHub Actions — OIDC permissions that, if missing, fail the publish job before upload.
- Understanding package.json Fields — where
name,version, andpublishConfigare declared.