Publishing Scoped Packages to npm
Scoped packages — names of the form @scope/name — give you a private namespace under a user or organization, eliminating the global name collisions that plague unscoped names. The trade-off is one extra rule: scoped packages publish privately by default, so a public first publish needs an explicit access flag. This page walks through the symptoms of getting that wrong, the exact steps to publish correctly, and the guardrails that keep it reliable in CI.
Symptoms
A scoped first publish that omits the public access opt-in fails immediately, before any tarball is uploaded:
npm error code E402
npm error 402 Payment Required - PUT https://registry.npmjs.org/@acme%2fwidget
npm error You must sign up for private packages
Or, when the scope exists but the access level is wrong for your plan:
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".
The 402 Payment Required message is the tell: npm is trying to publish a private scoped package and is asking you to pay for private hosting, when what you actually want is a public scoped package, which is free.
Root Cause
Scoped packages default to restricted (private) access. Unscoped packages default to public. This single difference, rooted in how npm models the @scope namespace, is the source of nearly every scoped first-publish failure. The registry will not infer that you meant "public" — you must declare it. Understanding default access alongside the rest of the publish lifecycle is covered in npm Registry Publishing Workflows.
Resolution Steps
- Confirm the name is correctly scoped. The
namefield must be@scope/name, all lowercase, with the scope matching a user or organization you belong to:{ "name": "@acme/widget", "version": "1.0.0" } - Declare public access in the manifest. Pinning it in
publishConfigmeans you never have to remember the flag again:{ "publishConfig": { "access": "public" } } - Or pass the flag explicitly on the publish command:
npm publish --access public - Verify the tarball before uploading so a scoped fix does not coincide with a contents bug:
npm pack --dry-run - Publish. With either the config or the flag in place, the public publish succeeds:
npm publish
Validation
Confirm the package is public and resolvable:
# Inspect the published access level and dist-tags
npm view @acme/widget
# Confirm it installs in a clean directory without auth
cd "$(mktemp -d)" && npm install @acme/widget
If npm view returns metadata anonymously, the package is public. If it 404s for logged-out users but resolves while authenticated, it published as restricted — set access to public and republish a new version (you cannot change access by re-pushing the same version; bump it).
Organization Scopes
When the scope is an npm organization (@acme) rather than a personal scope, two extra factors apply. First, your account must be a member of the org with publish rights to that package or team. Second, the org's default package visibility setting can be configured to public, which removes the per-publish access requirement for everyone on the team — useful for open-source orgs that never publish private packages. A granular access token scoped to the organization is the right credential for CI; broader token scope and 2FA mechanics are detailed in npm Registry Publishing Workflows.
Guardrails
- Set
publishConfig.access: "public"in every public scoped package's manifest so the access level is version-controlled, not flag-dependent. - Run
npm publish --dry-runin CI on every pull request to catch access and contents problems before a tag triggers a real publish. - For organizations, set the org-wide default package access to
publicif you never publish private packages, removing a recurring footgun. - Keep the
namescope consistent across a monorepo so every package inherits the same publish policy; the wider release setup lives under Package Publishing & Release Engineering.
Frequently Asked Questions
Why does npm ask for payment (402) when I publish a scoped package?
Because scoped packages default to restricted (private) access, and private packages require a paid plan. You almost certainly want a public scoped package, which is free — declare it with npm publish --access public or publishConfig.access: "public" and the 402 disappears.
Do I need a paid npm account to publish a public scoped package?
No. Public scoped packages are free. Payment is only required for private packages. The 402 error is npm interpreting the default restricted access as a private-package request.
Can I change a scoped package from restricted to public after publishing?
You change the access level with npm access public @scope/name, but the cleanest path is to set publishConfig.access: "public" and publish a new version. Access changes apply going forward; you cannot rewrite an already-published version's tarball.
Should I use my username or an organization as the scope?
For solo projects a personal scope (@yourname) is fine. For team-owned packages use an organization scope so membership, teams, and publish rights are managed centrally and a single org-wide default access setting governs every package.
Related
- Fixing npm publish 403 Forbidden Errors — when the failure is permissions or a name collision rather than access level.
- Setting Up npm Provenance with GitHub Actions — add signed attestations once your scoped package publishes cleanly.
- Understanding package.json Fields — how
name,publishConfig, andexportsinteract at publish time.