Fixing npm publish E404 Not Found
npm publish failing with E404 is confusing because you are trying to create something, and the registry says it cannot find it. The 404 is rarely about a missing package. The npm registry returns 404 for many authorisation failures on scoped packages, deliberately, so it does not reveal whether a private package or scope exists. The real causes are an unauthenticated request, a scope your account cannot publish to, a request sent to the wrong registry, or a mismatch in a trusted publishing configuration. This guide reads the error, narrows down the cause, and fixes each.
Exact symptoms and error messages
npm notice Publishing to https://registry.npmjs.org/ with tag latest and public access
npm error code E404
npm error 404 Not Found - PUT https://registry.npmjs.org/@acme%2fsdk - Not found
npm error 404
npm error 404 '@acme/sdk@1.4.0' is not in this registry.
npm error 404
npm error 404 Note that you can also install from a
npm error 404 tarball, folder, http url, or git url.
The last lines are generic install advice printed for every 404 — ignore them. The two useful facts are the method and URL (PUT https://registry.npmjs.org/@acme%2fsdk), which tell you which registry and package name the publish targeted.
Variants from other registries look similar:
npm error 404 Not Found - PUT https://npm.pkg.github.com/@acme%2fsdk - The requested resource could not be found.
Root cause analysis
For a PUT to a scoped package name, the npm registry answers 404 when the requester is not allowed to publish there — whether because they are not logged in, the scope belongs to someone else, or the package is private and the requester has no access. Returning 403 would confirm that the scope or package exists; 404 reveals nothing. How registry authentication works is covered in npm Registry Publishing Workflows.
The causes, from most to least common:
- Not authenticated for that registry. No token, an expired token, or a token configured for a different registry host (for example,
//registry.npmjs.org/:_authTokenset but the publish goes tonpm.pkg.github.com). - No permission on the scope. The
@acmescope is an npm organisation your account is not a member of, or a member without publish rights; or@acmeis someone else's user scope. - The scope does not exist. Publishing the first
@acme/*package requires an npm organisation or user namedacme. If neither exists, create the organisation first. - Wrong registry.
publishConfig.registryor a scoped registry mapping sends the publish somewhere unexpected — often a private registry that does not host this scope. - Trusted publishing mismatch. The OIDC identity does not match the package's trusted publisher, so the request is effectively unauthenticated.
Resolution
Check where the publish is going:
npm config get registry
npm config get @acme:registry
node -p "require('./package.json').publishConfig"
publishConfig.registry overrides everything for publishing. Make sure it matches where the package should live:
{
"name": "@acme/sdk",
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org/"
}
}
Check authentication for that exact registry:
npm whoami --registry https://registry.npmjs.org/
npm whoami --registry https://npm.pkg.github.com/
ENEEDAUTH or a 401 here confirms missing or invalid credentials. In CI, the token must be written for the right host — actions/setup-node with registry-url writes //<host>/:_authToken=${NODE_AUTH_TOKEN} into a project .npmrc, so NODE_AUTH_TOKEN must be set in the publish step's environment.
Check scope permissions:
npm org ls acme # are you a member? (org owners/admins can list)
npm access list packages @acme # which packages can you access?
npm access list collaborators @acme/sdk
If the organisation does not exist, create it on npmjs.com (free organisations can publish public packages). If it exists but you lack rights, an owner must add you to a team with read-write access to the package, as covered in Publishing Scoped Packages to npm.
For trusted publishing, compare the trusted publisher settings on the package with the workflow exactly — owner, repository, workflow filename and environment — as described in Publishing from CI with npm Trusted Publishing.
Token types and their scopes
npm's granular access tokens add another way to hit a 404: a token can be valid, authenticate successfully for npm whoami, and still lack permission for the package you are publishing. Granular tokens are scoped to specific packages, scopes or organisations, with read-only or read-write permission, an expiry date and optional IP ranges. A token created for @acme/ui cannot publish @acme/sdk, and the registry answers that attempt the same way it answers an anonymous one.
List your tokens with npm token list (or on the npm website under Access Tokens) and check the package and permission columns. For CI, prefer trusted publishing where supported; where a token is unavoidable, create one granular token per repository, limited to the packages that repository publishes, with read-write permission and an expiry that matches your rotation schedule, as described in Rotating npm Publish Tokens Without Breaking CI.
Organisations add one more layer: an organisation can restrict which members and tokens may publish, and packages can require two-factor authentication for publishing. A token that bypasses 2FA must be explicitly allowed by the package's publishing settings; otherwise the registry may respond with 403 or, for some scoped cases, the same opaque 404.
Worked example: publishing to the wrong registry in CI
A team moves @acme/sdk from GitHub Packages to the public npm registry. The first release job fails with 404 Not Found - PUT https://npm.pkg.github.com/@acme%2fsdk. The URL gives it away: the repository's committed .npmrc still contains @acme:registry=https://npm.pkg.github.com, which overrides the default registry for the scope. Adding publishConfig.registry set to the public registry fixes the publish, and the stale scope mapping is removed from .npmrc once consumers have migrated. Routing between registries is covered in Routing Scopes to Multiple Registries in .npmrc.
Monorepo release jobs
In a monorepo, one release job publishes many packages in a row, and E404 often hits only some of them. That pattern is informative. If the failing packages share a scope the others do not use, the token or trusted publisher configuration lacks that scope. If they are new packages being published for the first time, the organisation's default permissions may not grant the release team write access to new packages, or trusted publishing cannot be configured yet because the package does not exist. If they are the packages with a publishConfig.registry override, they are going somewhere else entirely.
Release tools such as Changesets stop at the first failure by default, leaving later packages unpublished and the release half-done. Run a dry-run publish for every package in the release before the real run, and when a partial release does happen, fix the cause and re-run the publish step — tools skip versions that are already on the registry, so re-running is safe.
Debugging with verbose output
npm publish --dry-run --loglevel=verbose 2>&1 | grep -Ei "registry|auth|publishing"
npm publish --loglevel=http 2>&1 | grep -E "PUT|GET" | head
--dry-run shows the resolved registry and access settings without uploading. --loglevel=http prints each request with its status, which confirms whether an authorization header was sent (npm logs that a token is used without printing it).
Prevention and CI/CD guardrails
- Set
publishConfig.registryexplicitly in every published package. - Run
npm whoami --registry <url>as a step before publishing in CI, so authentication failures are reported clearly. - Create npm organisations for your scopes before the first publish, and manage access through teams.
- Dry-run releases when changing registries, as covered in Dry-Running a Publish Before Release.
Frequently Asked Questions
Why does npm say the package is not in the registry when I am publishing it? That message is generic 404 text. For publishes it almost always means the registry refused the request without revealing why — typically authentication or scope permissions.
Can a 404 mean the version already exists? No. Publishing an existing version returns 403 with "cannot publish over the previously published versions", covered in Fixing npm 'Cannot Publish Over Previously Published Version'.
Does unscoped publishing ever return 404? Yes, when authentication is missing or the request goes to a registry that does not accept it. Unscoped name conflicts return 403 instead.
Why does it work from my laptop but not from CI?
Your laptop uses credentials from your user ~/.npmrc (from npm login), which CI does not have. CI relies on the token or OIDC configuration in the workflow, which is where the problem is.
Related
- npm Registry Publishing Workflows explains authentication and publishing.
- Fixing npm publish 403 Forbidden Errors covers the neighbouring status code.
- Publishing Scoped Packages to npm sets up scopes and access correctly.
- Fixing npm 401 Unauthorized on a Private Registry handles credential problems in depth.