nextpm has two ways to publish. A person or a CI identity is behind each release, and no token can publish alone.
| From | Credential | Approval |
|---|---|---|
| A terminal | the token from npm login |
your passkey, for each change |
| GitHub Actions | the job's identity, with no stored secret | a maintainer's passkey, for each release, unless you turn it off |
A token from Settings, from npm token create, or of an Org can only install.
Publish from a terminal#
- Sign in with
npm login. - Run
npm publish. npm prints the address of an approval page onexample.com, and waits. - Open the address. The page shows the change, such as
Publish @alice/hello@1.0.0and its tarball hash, and where the request came from. - Check the change, and select Approve with passkey. npm then finishes the publish.
npm 9 and later, pnpm, and Bun show the approval address. Yarn cannot publish to nextpm yet: it asks for a one-time code, and nextpm has none.
Each change of the token waits for an approval in the same way: npm unpublish, npm deprecate, npm dist-tag, npm owner, npm access, npm trust, npm stage approve, and npm stage reject. npm logout and npm stage publish need no approval.
An approval is for one request only: the same token, the same change, and the same body. It expires after 5 minutes.
Warning
Approve only a change that you started. A change that you did not start means that someone has your token. Select Refuse, then revoke the token in Settings, under Tokens. The page also warns you when your browser is on a different network from the request.
Publish from GitHub Actions#
With trusted publishing, a GitHub Actions job publishes with its own identity. You store no token in the repository. npm 11.5.1 and later does this by itself.
Link GitHub#
The registry trusts only repositories that you can prove are yours.
- For a package in your user scope, link your GitHub account: Settings, then Link GitHub.
- For a package of an Org, an owner of the Org links its GitHub organization, on the Org's GitHub page.
Add a trusted publisher#
Open the package's page, then Settings. Under Add a trusted publisher, enter:
| Field | Example | Meaning |
|---|---|---|
| Repository | alice/hello |
the repository that runs the workflow |
| Workflow file | release.yml |
the file in .github/workflows that publishes |
| Environment | npm |
the GitHub environment of the publish job |
Approve the change with your passkey. Or add it from a terminal:
npm trust github @alice/hello --repo alice/hello --file release.yml --env npmA name that is not published yet can have a trusted publisher too. Open Settings, then Publishing, and enter the name under Publish a new package from CI. Its first release from CI creates the package.
The registry pins the ids of the repository and its owner, not their names. A renamed repository keeps working, and a new repository with an old name never matches.
Protect the environment#
The environment is required. In the repository's settings, under Environments, limit the environment to tags, or require a reviewer. Then a change to a workflow on a branch cannot publish.
Add the workflow#
The package's Settings page shows this file with your values:
on:
release:
types: [published]
jobs:
publish:
runs-on: ubuntu-latest
environment: "npm"
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
with:
node-version: 24
registry-url: https://registry.example.com/
- run: npm install -g npm@latest
- run: npm ci
- run: npm stage publishApprove each release#
By default, each release from CI waits in the package's stage until a maintainer approves it with a passkey. So a workflow that someone changed cannot ship a version alone.
On the website, the releases that wait for you are under Settings, then Publishing. Each package also lists its own releases on its Settings page, under Waiting for approval. Select Download to check the tarball, then Approve or Reject. Your passkey confirms each one.
From a terminal:
npm stage list
npm stage approve <id>
npm stage reject <id>To let CI publish with no approval, select Let CI publish with no approval on the package's Settings page. Then change the last step of the workflow to npm publish. The first release of a new package always waits for approval.
What the registry checks#
The registry gives the job a token for each release only when all of these are true:
- The identity comes from
https://token.actions.githubusercontent.com. - Its repository, repository owner, workflow file, and environment match a trusted publisher of the package.
- The event is
push,release,workflow_dispatch, orschedule. A pull request, a comment, or aworkflow_runcan run code that an outsider controls, so the registry refuses these. - The runner is hosted by GitHub, not self-hosted.
- The identity was not used before.
The token can publish only this package, for 15 minutes. It cannot change tags, owners, access, or other packages. A provenance bundle in the release must name the same repository.
What nextpm does not allow#
Attackers used each of these, or the same weakness, to publish malware.
| Not allowed | Why |
|---|---|
| A token that publishes alone, such as an automation token | Stolen tokens spread the Shai-Hulud worm, and published the nx and eslint-config-prettier attacks. |
| An Org token that publishes | A shared CI secret has no person behind it. Org CI publishes with trusted publishing. |
| One-time codes | A phishing site can relay a live code, as in the chalk and debug attack. A passkey works only on example.com. |
| A recovery code that approves a change | A person can type it into a phishing site. A recovery code can only add a new passkey. |
| A password sign-in from the CLI | Passwords can be phished, and stuffed from other sites' leaks. npm login --auth-type=legacy gets 403. |
| A window of time in which a token can publish with no check | Code on your machine can use the window. Each approval is for one change. |
| Trusted publishing from a pull request or a self-hosted runner | An outsider's code can run there, as in the Ultralytics release on PyPI. |
The API reference has the full rules.