How To Publish To NPM From GitHub Actions

Using The New NPM OIDC Trusted Publishing Workflow

At the end of 2025, NPM registry revoked all personal NPM tokens that I used to publish new NPM package releases. This change improves the security of the entire NPM publishing workflow, but has disrupted my CI process. For example, the new feature of cypress-timestamps has not been released, failing with the error "SemanticReleaseError: Invalid npm token.". Hmm, what do we do now?

NPM publishing error: invalid token

We could use publish to NPM using local npm CLI commands, entering the 2FA token, etc. But I really hate this idea. I have more than 400 NPM packages, so the release process MUST be automated and be performed by CI. So let's look at the trusted publishing where NPM "knows" that a particular workflow from GitHub Actions is allowed to publish (and no one else). I already use trusted publishing to publish NPM packages cypress-split and cypress-map, so it should be simple to apply the same steps to cypress-timestamps.

First, go to the package settings under your NPM registry account.

cypress-timestamps settings on NPM registry

The top settings section is for configuring the trusted publishing. Seems both GitHub Actions and GitLab CI providers can be configured as of this writing (February 2026). I am using GHA, so I will click "GitHub Actions" button.

Package settings includes trusted publishing config

Enter the GitHub username (organization name) and the repository name, and the name of the workflow file (inside the .github/workflows folder). In my case, I am pointing at the ci.yml file.

Tell NPM registry where the GHA workflow for the package is

Enter the 2FA token if needed

2FA is needed to connect GHA to NPM registry

You should see the "success" banner.

Trusted publishing has been configured

Great, let's now update the ci.yml file. We can remove the old NPM token and bump the semantic release. For clarity, I added comments to the workflow code to explain each step

ci.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
name: ci
on: push

jobs:
test:
runs-on: ubuntu-24.04
permissions:
# allow the release step to comment on
# issues, pull requests, and write to the repo
contents: write
issues: write
pull-requests: write
# Required for OIDC to let NPM registry know
# that this workflow is trusted
id-token: write

steps:
# important: the default Node version is v18
# which gives us an auth error trying to release
# so install v24 explicitly
- uses: actions/setup-node@v6
with:
node-version: 24

- name: Checkout ๐Ÿ›Ž
# https://github.com/actions/checkout
uses: actions/checkout@v6

- name: Run tests ๐Ÿงช
# https://github.com/cypress-io/github-action
uses: cypress-io/github-action@v6

- name: Check type declarations ๐Ÿ”Ž
run: npm run typecheck

- name: Semantic Release ๐Ÿš€
# https://github.com/cycjimmy/semantic-release-action
uses: cycjimmy/semantic-release-action@v6
with:
branch: main
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

I try to limit the GITHUB_TOKEN permissions to each job, if possible. The important GHA to NPM registry bit is the id-token: write that lets NPM "know" that this CI workflow is legit and is allowed to publish new NPM versions. We can see the successful GHA workflow

Successful CI workflow including the release step

We can see the new package version on NPM

NPM version v1.5.0 has been published

Nice.