npm publish "403 Forbidden - You do not have permission to publish" in CI
npm authenticated your token but the registry rejected the publish: the account behind the token is not a maintainer of that package name, or the scope belongs to someone else. A 403 is an authorization failure, not a missing-login.
What this error means
npm publish stops with "npm ERR! code E403" and "403 Forbidden - PUT https://registry.npmjs.org/<package> - You do not have permission to publish '<package>'. Are you logged in as the correct user?"
npm ERR! code E403
npm ERR! 403 403 Forbidden - PUT https://registry.npmjs.org/my-pkg - You do not have permission to publish "my-pkg". Are you logged in as the correct user?
npm ERR! 403 In most cases, you or one of your dependencies are requestingCommon causes
The token belongs to an account that is not a maintainer
The CI NODE_AUTH_TOKEN authenticates a user or automation token that was never added as a maintainer of the package, so the registry forbids the write.
The package name (or scope) is already owned by someone else
Publishing a new unscoped name that another account already registered returns 403, because you cannot publish into a name you do not own.
How to fix it
Confirm the token identity and maintainer list
- Run
npm whoamiin the job to confirm which account the token represents. - Add that account as a maintainer with
npm owner add <user> <package>, or use a token from an existing maintainer. - For a brand-new name, publish under a scope you control (for example
@yourorg/my-pkg).
npm whoami
npm owner ls my-pkgSet the auth token from a secret in the workflow
Use setup-node with a registry URL and pass an automation token via NODE_AUTH_TOKEN so the publish runs as a maintainer account.
- uses: actions/setup-node@v4
with:
node-version: 20
registry-url: 'https://registry.npmjs.org'
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}How to prevent it
- Publish under an npm scope your org owns so name ownership is unambiguous.
- Use an automation token from a maintainer account, stored as a CI secret.
- Keep the package maintainer list in sync with who holds the publishing token.