Back to publishing & release Automate semantic versioning Publish to the npm registry Harden the supply chain

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:

Exact Symptoms The most common 403 strings, copied verbatim from npm's output: Exact Symptoms The most common 403 strings, copied verbatim from npm's output:
Exact Symptoms — the core idea of this section at a glance.
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.

403 root-cause decision tree A 403 error branches by message into version-exists, name-taken, permission, and access-level causes, each with a distinct fix. 403 Forbidden read message "over previously" version exists "too similar" name taken "no permission" token / 2FA restricted access level targeted fix per cause
Branch on the verbatim message: each 403 variant maps to one root cause and one targeted fix.

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

  1. Confirm who you are authenticated as:
    npm whoami
    If this errors, you have a 401, not a 403 — re-authenticate first.
  2. Check whether the version already exists:
    npm view @acme/widget versions --json
    If your target version is listed, bump it:
    npm version patch    # or minor / major
  3. Verify your token can write to this package:
    npm access list packages   # confirm read-write for the scope/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.
  4. Supply an OTP for interactive 2FA publishes:
    npm publish --otp=123456
  5. Set public access for a scoped package:
    npm publish --access public
  6. Rename if the name is taken by moving under a scope you own:
    { "name": "@yourscope/widget" }
Resolution Steps If this errors, you have a 401, not a 403 — re-authenticate first. Resolution Steps If this errors, you have a 401, not a 403 — re-authenticate first.
Resolution Steps — the core idea of this section at a glance.

Match the fix to what the 403 message says:

# Name taken (unscoped): publish under your scope instead
npm publish --access public   # for @you/name

# 2FA required: supply a one-time password
npm publish --otp=123456

# Version already exists: bump before publishing
npm version patch && npm publish

For a token that lacks rights, mint one with publish access for the package or organization; for a name collision, adopt a scoped name you own. Verify identity and rights first with npm whoami and by checking the package's access settings.

Validation

Validation Validation in production JavaScript package workflows. Validation Validation in production JavaScript package workflows.
Validation — the core idea of this section at a glance.
# 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-run in 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.access in the manifest so the access level cannot drift between machines.
Prevention Prevention in production JavaScript package workflows. Prevention Prevention in production JavaScript package workflows.
Prevention — the core idea of this section at a glance.
  • Prefer scoped names you own to avoid unscoped name-collision 403s entirely.
  • Use a token with explicit publish rights for the package or organization.
  • Automate version bumping so a publish never collides with an existing version.
  • Verify auth and rights early in CI with npm whoami so a 403 fails fast and clearly.

Reading 401, 403, and 409 apart

npm's publish failures come with distinct status codes that point at distinct fixes, and treating them interchangeably wastes time. A 401 is an authentication failure — no valid credential reached the registry, so the token is missing, wrong, or bound to the wrong host. A 403 is an authorization failure — the credential authenticated but the action is refused, whether because the name is taken, the token lacks rights, 2FA is required, or the version is immutable. A 409 (or a 403 worded as such) specifically means the version already exists and cannot be overwritten.

Publish status codes Which layer to fix based on the code. Which code? 401 token / host binding 403 name / rights / 2FA 409 bump the version
401 is the credential, 403 is the permission or name, 409 is the version.

The diagnostic order follows from the codes. On a 401, check the _authToken host binding and that the environment variable expanded. On a 403, read the message: a name collision means adopt a scoped name, a rights problem means mint a token with publish access, a 2FA prompt means supply an OTP. On a version conflict, bump the version. Reading the code and message first — rather than regenerating tokens blindly — routes you to the right layer immediately, which is the difference between a five-minute fix and an hour of guessing at a publish that the registry is refusing for a reason it already told you.

Why scoped names avoid the most common 403

The single most common first-publish 403 is a name collision on an unscoped name, and scoping eliminates it by construction. An unscoped name lives in npm's shared global namespace, where any name you might choose could already be published by someone else — and if it is, you have no rights to publish over it, so the registry returns 403. A scoped name (@you/name or @org/name) is namespaced to an identity you control, so within your scope you can publish any name that is not already taken by you, and no one else's package can collide with it.

Unscoped vs scoped A global-namespace collision versus a scope you own. Unscoped name • shared global namespace • name may be taken • 403, no rights Scoped name • namespaced to you • no external collision • publish freely in your scope
A scoped name is namespaced to your identity, so no one else's package can collide.

Beyond avoiding collisions, scoping brings the access-control and private-registry benefits that make it the right default for most packages. A scope can be routed to a private registry, published public or restricted, and used to close dependency confusion. So adopting a scoped name is not just a workaround for a naming 403 — it is the more capable choice that also happens to sidestep the most frequent publish error. For a new package, choosing a scope you own from the start avoids the collision entirely and sets up the access control you will likely want anyway.

Preventing publish failures in CI

The most reliable way to stop a 403 from surfacing mid-release is to verify authorization early and derive every publish input rather than typing it. A CI job that runs npm whoami against the target registry before attempting a publish fails fast with a clear message if the credential is missing or lacks rights, rather than proceeding to a confusing 403 at the publish step. Confirming the package's access settings and the token's scope up front turns an authorization problem into an early, named failure.

Prevent publish 403s Verify auth early, derive version and access. whoami early auth fails fast derive version no collision fix access no private default
Verifying rights early and deriving inputs turns 403 causes into early or prevented failures.

Deriving the version and access level removes the other common 403 causes. Automating the version bump so it is computed from the changes means a publish never collides with an existing version — the immutable-version 403. Fixing the access level in publishConfig means a scoped package cannot fail for defaulting to private. Publishing from CI with an organization-scoped token or an OIDC identity means the credential always has the right rights. Together, these turn publishing from a hand-typed command that can fail in several authorization-related ways into a reproducible operation where the failures are caught early or prevented by construction, which is what keeps a release pipeline from stalling on a 403 nobody expected.

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.

What's the difference between a 401 and a 403 on npm publish?

A 401 is an authentication failure — no valid credential reached the registry. A 403 is an authorization failure — the credential authenticated but the action is refused, because the name is taken, the token lacks rights, 2FA is required, or the version is immutable. Read the 403 message to route to the right fix.

Why do I get a 403 publishing a new package?

Usually a name collision: the unscoped name is already published by someone else, so you have no rights to it. Publish under a scope you own (@you/name) to sidestep it entirely, which also enables access control and private-registry routing.

How do I fix a 403 that mentions two-factor authentication?

Supply a one-time password with npm publish --otp=123456, or configure an automation token that satisfies the 2FA requirement for CI. The 403 in this case is the registry requiring a second factor the publish did not provide.

How do I stop 403 errors from breaking my release pipeline?

Verify authorization early with npm whoami against the target registry so a missing or under-scoped credential fails fast, automate the version bump so a publish never collides with an existing version, and fix the access level in publishConfig so a scoped package cannot default to private.

Can I publish over a version to fix a 403 about an existing version?

No — published versions are immutable, which is why the registry returns the error. Bump to a new version (npm version patch) and publish that; if the existing version is broken, deprecate it with a message pointing at the fix.

Why does my automation token get a 403 when my personal login works?

The automation token likely lacks publish rights for the package or organization, or the package requires 2FA that an automation token is not configured to satisfy. Mint an automation token with explicit publish access for the scope, or use OIDC publishing so the CI identity carries the right permissions.

Does a 403 ever mean the registry itself rejected the package?

Occasionally — a registry can refuse a publish that violates a policy, such as a package exceeding a size limit or a name matching a blocked pattern. The 403 message names the reason; if it is not auth, rights, 2FA, or an existing version, read it for a policy violation specific to that registry.

Related

npm Registry Publishing Workflows