Scoping GitHub Packages to a Single Organization
Consumers can install your @org/* packages from GitHub Packages only if their client is configured and authorized for that org. This page shows the exact configuration and the permission mistakes that block installs.
Exact symptoms and error messages
Installs of an org-scoped package fail with a 404 or 401 despite the package existing:
npm error 404 Not Found - GET https://npm.pkg.github.com/@org%2fwidgets
npm error 404 '@org/widgets@^1.0.0' is not in this registry.
Root cause analysis
GitHub Packages resolves @org/* only when the scope is mapped to npm.pkg.github.com and the token has read:packages plus access to the org. A 404 usually means the scope is not mapped (so npm asks the public registry, which does not have it); a 401 means the token lacks org access. This is the per-scope routing model from Private Registries and Access Control.
The 404-not-401 behavior confuses people because it looks like the package is missing. What actually happens is a routing failure: if @org is not mapped to npm.pkg.github.com, npm sends the request to the default public registry, which has never heard of your private package and correctly returns 404. The package exists; npm just asked the wrong server. This is the per-scope routing model that makes private and public dependencies coexist in one install.
When the scope is mapped and you still get 404 or 401, the cause shifts to authorization: the token lacks read:packages, or the consuming account or repository has no access to the org's packages. GitHub ties package visibility to repositories and org membership, so a token that can read the source may still be unable to read the package unless it is granted package access explicitly.
Resolution and configuration patch
# .npmrc
@org:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}
Grant the token (or the consuming repository, via permissions: packages: read) access to the org's packages, and set package visibility so member repositories can resolve it.
For a consuming GitHub Actions workflow, grant the built-in GITHUB_TOKEN package read access rather than minting a personal token:
jobs:
build:
permissions:
contents: read
packages: read
steps:
- uses: actions/setup-node@v4
with:
registry-url: https://npm.pkg.github.com
- run: npm ci
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
The packages: read permission lets the job's token resolve org-scoped packages without a long-lived personal access token, which keeps the credential scoped to the job and expiring with it.
CLI validation and debug commands
# Confirm the scope maps to GitHub Packages
npm config get @org:registry
# Verify token identity against the host
npm whoami --registry=https://npm.pkg.github.com
# Try the install with HTTP logging
npm install @org/widgets --loglevel=http
Prevention and CI guardrails
- Map the org scope in a committed
.npmrc(auth token referenced, not inlined). - Grant consuming workflows
permissions: packages: readsoGITHUB_TOKENcan resolve. - Set package visibility deliberately — internal packages should stay org-visible only.
- Document the required token scopes so new consumers do not hit a silent 404.
Package visibility and who can install
GitHub Packages ties install access to package visibility and org membership, which is a different axis from repository access. A package can be private to the org, visible to selected repositories, or public, and a consumer resolves it only if their identity is on the right side of that setting. Getting a 404 or 401 despite a correct .npmrc usually means the visibility or grant is the missing piece, not the scope mapping.
For internal packages, the goal is usually 'installable by any repo in the org, invisible outside it'. Set the package's visibility to the org, grant the consuming repositories access, and use GITHUB_TOKEN with packages: read in their workflows. External collaborators need an explicit grant and a token carrying read:packages. Thinking of it as two separate questions — is the scope routed to GitHub Packages, and is this identity allowed to see the package — keeps the debugging fast.
Publishing to the org registry from CI
Publishing an org-scoped package is the mirror image of consuming one: the scope must route to GitHub Packages, and the token needs write:packages plus access to the org. Doing it from CI with the job's token keeps a standing personal token out of the picture.
permissions:
contents: read
packages: write
steps:
- uses: actions/setup-node@v4
with:
registry-url: https://npm.pkg.github.com
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Setting publishConfig.registry in package.json pins the publish to GitHub Packages regardless of the local .npmrc, so a developer's default registry cannot accidentally push an internal package to the public index — a small guard that prevents an embarrassing leak.
Frequently Asked Questions
Why does the install 404 instead of 401?
Because the scope is not mapped to GitHub Packages, npm queries the public registry, which genuinely does not have your private package — so it returns 404 rather than an auth error.
Can external collaborators install org packages?
Only if granted access to the org's packages and using a token with read:packages. Package visibility and org membership govern who can resolve them.
Why do I get a 404 when the package clearly exists?
Because @org is not mapped to GitHub Packages, so npm asks the public registry, which does not have your private package and returns 404. Map the scope with @org:registry=https://npm.pkg.github.com and the request goes to the right host.
Can any repo in my org install the package?
Only if the package's visibility and grants allow it. Set the package visible to the org, grant consuming repositories access, and give their workflows packages: read. Repository access alone does not imply package access.
How do I stop an internal package leaking to the public registry?
Set publishConfig.registry to GitHub Packages in package.json so the publish is pinned there regardless of a developer's default registry, and keep the package's visibility scoped to the org.
Related
- Private Registries and Access Control — the overview for private distribution and access control.