DEV Community

Cover image for Your NPM_TOKEN stops publishing in January 2027. Here's the one-command migration
Markus
Markus

Posted on AI-assisted

Your NPM_TOKEN stops publishing in January 2027. Here's the one-command migration

If your GitHub Actions release publishes to npm with a NPM_TOKEN secret, it has an expiry date. From January 2027, a granular token that bypasses 2FA can no longer publish on its own (npm docs). Since August 2026 you've probably seen this warning in your release logs:

npm notice npm tokens that bypass 2FA are being restricted for account changes and direct publishing.
Enter fullscreen mode Exit fullscreen mode

The replacement is trusted publishing. GitHub signs a short-lived OIDC token for your workflow run, npm exchanges it for a publish token, and nothing long-lived is stored anywhere. You also get a provenance badge on every release for free.

The idea is simple. The migration has a handful of sharp edges.

What actually has to change

For a typical release workflow:

  1. Give the job an OIDC token. Add permissions: id-token: write to the publishing job. If the job had no permissions block before, keep whatever else it needs, such as contents: write for tagging.
  2. Use npm 11.5.1 or newer. This is the one that bites. Node 22 ships npm 10, so you need Node 24 or an npm install -g npm@^12 step before publishing.
  3. Remove the token. Delete NODE_AUTH_TOKEN / NPM_TOKEN from the publish step and the job. npm falls back to a configured token, so leaving it in keeps the old token in use.
  4. Set registry-url on actions/setup-node.
  5. Fix repository.url in package.json. npm checks it against the repository the workflow runs in.
  6. Connect the package to the workflow on npm. Either open npmjs.com → package → Settings → Trusted publishing, or run:
   npm trust github my-pkg --repo me/my-repo --file release.yml --allow-publish --yes
Enter fullscreen mode Exit fullscreen mode

The workflow file name must match exactly. With reusable workflows, it's the calling workflow's file.

  1. Delete the secret after the first tokenless release.

The errors you'll see if one step is missing

These messages don't point at the cause. Here's what each one usually means.

npm error code ENEEDAUTH

No id-token: write, npm older than 11.5.1, or a workflow file name that doesn't match the trusted publisher exactly (it's case-sensitive and includes .yml).

npm error 404 Not Found - PUT https://registry.npmjs.org/...

Usually the same causes as above, or a GitHub environment that doesn't match the one on the trusted publisher. It's also what an expired token looks like, which is how most people find out.

npm error code E422 ... repository.url

package.json repository is missing, or doesn't match the GitHub repository.

Doing it in one command

To make this a one-liner, I built go-tokenless (MIT). From the root of the repo:

npx go-tokenless          # preview: lists every change and shows a diff, writes nothing
npx go-tokenless apply    # makes the changes
Enter fullscreen mode Exit fullscreen mode

It edits only the lines that need changing, keeping your comments and layout, and then prints the exact npm trust github … command for each package. It handles npm, pnpm, Yarn Berry, changesets, semantic-release, release-please, Lerna/Nx and reusable workflows. It also follows publishes hidden inside ./scripts/*.sh, make targets and composite actions.

A few things it deliberately won't do. In these cases it stops and explains instead of guessing:

  • Fork-reachable publishing: it won't grant id-token: write to a publish job in a workflow triggered by pull_request_target, issue_comment or workflow_run. That would let a fork publish your package.
  • Self-hosted runners: it won't migrate a job on a self-hosted runner, because npm's trusted publishing doesn't accept them.
  • Ambiguous edits: it won't touch anything when an edit would change more than the migration. It compares the workflow before and after, by meaning, before writing.

If your installs need private packages from your npm org, --read-token NPM_READ_TOKEN gives the install steps a read-only token, and publishing stays tokenless.

For AI coding agents

It ships as an MCP server and an Agent Skill too, so you can just ask your agent to "move our npm publishing to trusted publishing":

claude mcp add go-tokenless -- npx -y go-tokenless mcp
Enter fullscreen mode Exit fullscreen mode

Before January

Run npx go-tokenless in your repos. It's read-only, takes seconds, and tells you whether each one is already tokenless. If it isn't, the diff is usually five lines.

Disclosure: I maintain go-tokenless. Issues and PRs welcome: https://github.com/Continuous-Actions/go-tokenless

Top comments (0)