devops
Run Multiple npm Publishing Scripts with Trusted Publishing (OIDC) via GitHub Reusable Workflows
npm only allows one GitHub Actions workflow script, but that script can dynamically call different publishing workflows.
Introduction
In the last few months, the npm ecosystem has been plagued by supply chain attacks where OSS maintainers of widely used packages have been phished and versions of these packages containing malicious code have been published to npm for download. Luckily, the broader software community has noticed the compromised libraries early and removed the bad package versions quickly, but it is an ongoing problem.
To try and curb these attacks, GitHub announced changes to strengthen the security around the npm ecosystem in the form of deprecating long-lived publishing tokens stored in GitHub projects and strongly encouraging everyone to begin using trusted publishing (OIDC) workflows, which use temporary, job-specific credentials to publish to npm. These changes were to take effect in November of 2025.
I've previously written about how I created and maintain a JavaScript SDK for my company's API with the help of GitHub Action workflows, and since I first built that repo a couple of years ago, its use has expanded such that it now has two separate npm publishing workflows that run at different times. One workflow publishes new stable versions of the SDK for public use, and one workflow publishes beta versions of the SDK that the dev team can use for internal testing.
When I tried to follow the instructions for setting up trusted publishing in npm, I was dismayed to learn that currently npm only allows for one workflow file to be declared as a trusted publisher.
So what's an npm library to do that has more than one publishing workflow? Trust the production deploy script and relegate the other scripts to using expiring tokens that will have to be manually updated on a regular basis? ðŸ«
But fear not! All hope is not yet lost.
After turning to the GitHub Discussion thread where people could ask questions and rage express their feelings about the changes, I wrote about my own use case (2 different workflow files that both need to publish to npm regularly) and someone suggested a solution I'd never come across before: GitHub Reusable Workflows.
After a couple of false starts and a little more assistance from the dev community, I had trusted publishing working for both npm publishing workflows and my crisis was averted! 🙌
Today, I'll show you how to use trusted publishing (OIDC) on npm with multiple GitHub Actions workflow scripts thanks to GitHub Actions reusable workflows.
NOTE: Full disclosure, before I turned to other actual devs, I tried to tackle this problem with just the help of Claude Code and it was useless.
It had zero ideas of how to fix this seemingly intractable problem of two publishing scripts with only one trusted publishing input in npm, which just goes to show that as soon as the coding problem gets less common, the assistance of AI agents starts to fail.
In this case, humans FTW!
GitHub Actions reusable workflows
Before we dive into the solution, let's talk about what even are GitHub Actions reusable workflows, because as I said, I'd never even heard of them before now.
GitHub Actions reusable workflows are YAML-formatted files like any other GitHub Actions workflow file, but they allow for the encapsulation of common CI/CD actions that can be called by other workflows, eliminating the need to copy and paste the same workflow logic across different projects, orgs, or even public repos.
For instance, if you deploy multiple web apps to the same cloud environment, a reusable workflow could encapsulate the entire deployment logic, or a reusable workflow could automatically fix code styling issues to ensure consistent code formatting across projects, or (in my case) a reusable workflow can build and publish different versions of an SDK to npm.
As I learned, the key to successfully using a GitHub Actions reusable workflow for trusted publishing with npm is that npm validates the entry point workflow, not the workflow that actually runs the npm publish command.
What I'll cover next, is how to create a single entry point workflow file that will be designated as the Trusted Publisher in npm, and that file will then call the appropriate GH Actions workflow file to do the actual publishing steps.
Create a publish-switch.yml file in the .github/workflows/ folder
So first things first: create a new single entry point workflow file inside of the project repo's .github/workflows/ folder where the other GH Actions publishing workflow files already live.
For convenience, I named this new file publish-switch.yml. This is the file name that will be set in the npm project's Settings tab as the Trusted Publisher, which I'll get to shortly.
Inside of this new publish-switch.yml file, there are two lines of permissions required to activate the OIDC trusted publishing. The permissions for OIDC need to be in this reusable workflows file (and not the files it will call to publish to npm) because publish-switch.yml is the file that npm validates against when doing the actual publishing.
The lines you'll need to include are:
permissions:
id-token: write # required for OIDC to request ID token to authenticate workflow
contents: read # switch this to 'write' too if the workflow also needs to push back content to repo (like if you automatically increment an SDK version before publishing)The full publish-switch.yml may look something like the following. I'll explain what's going on beneath it.
name: Publish switch
on:
release:
types: [created]
push:
branches:
- "test-release-**"
paths:
- "openapi.yaml"
permissions:
id-token: write # Required for OIDC
contents: write # Required for npm beta version updates
jobs:
publish-prod:
if: github.event_name == 'release'
uses: ./.github/workflows/publish-prod-npm.yml
permissions:
id-token: write
contents: read
publish-beta:
if: github.event_name == 'push'
uses: ./.github/workflows/publish-beta-npm.yml
permissions:
id-token: write
contents: writeFor the SDK that I publish to npm, there's two ways the different publishing workflows are triggered.
- When a new release is created.
- This event triggers a production release to npm after I review the changes to the SDK, bump the package version, and manually create a new release in GitHub.
- When a new branch is created whose name begins with
test-release-**.- This event triggers an automated release to npm where a new version of the SDK is built, tagged with a
-beta.XXat the end of the current latest version, and published as the latest beta release on npm.
- This event triggers an automated release to npm where a new version of the SDK is built, tagged with a
In my reusable workflow file above, I:
- Designate event triggers for each type of GH Actions workflow, and specify those same triggers in this file,
- Add the required
permissionsfor OIDC trusted publishing, - And then, based on which GitHub
event_nametriggered the release, determine which workflow publishing file to run (prod publishing or beta publishing).
NOTE: I've omitted the particulars of the actual GitHub Actions publishing files themselves, because they're outside the scope of this article, but if you'd like to see them in their entirety, the production publishing workflow file is here, and the beta publishing workflow file is here.
Configure the npm repo's Trusted Publisher to point towards the publish-switch.yml file
Now that the GH Actions reusable workflow file is created, it must be set as the Trusted Publisher file inside of the npm repo's settings.
To do that:
- Navigate to the npm project's "Settings" tab (I believe you'll need at least Maintainer level access to the repo to be able to get there),
- Select the button for your publisher (at the time of writing this only GitHub and GitLab are available),
- Configure the following fields:
- Organization or user: GitHub username or organization name
- Repository: Repository name
- Workflow filename: The filename of the GH Action workflow inside the repo doing the deployment to npm. (In my case it is
publish-switch.yml.)
- Then click the Save Changes button, and it's done.
Voilà ! OIDC trusted publishing to npm
All that's left to do at this point is check that the publish-switch.yml workflow is working correctly.
Test npm beta publishing workflow
For me, I tested the npm beta publishing workflow by creating a new test branch in my repo with the name test-release-oidc-publish, and watched the publish-switch.yml file correctly select the publish-beta-npm.yml workflow file to run.
To verify it correctly deployed, I checked the GH Actions workflow logs, and at the bottom of the "Publish Beta Version to npm" step, I can see the text, "npm notice publish Signed provenance statement with source and build information from GitHub Actions".
Test npm production publishing workflow
Then, to test the npm production publishing workflow, I created a new release of the SDK code, and once more, watched the publish-switch.yml file correctly identify that this event should run the publish-prod-npm.yml workflow file.
Once again, I checked the GH Actions workflow logs, saw the successful publish message at the bottom of the "Publish to npm" step, "npm notice publish Signed provenance statement with source and build information from GitHub Actions."
Success!
NOTE: For one more gut check, I could also visit the npm website and make sure the new beta or production versions are available for download there.
Conclusion
The democratization of coding over the last couple of years has made it much easier for people without backgrounds in computer science to bring their ideas to life online, but it's also made it easier for bad actors to add hard-to-detect malware in popular, previously innocuous software libraries with hundreds of thousands or millions of downloads per week.
GitHub stepped up by quickly introducing new, more secure ways of controlling JavaScript libraries published to the npm package registry and swiftly deprecating older methods more prone to potential hacking. And while this is a good move in theory, since it's been rolled out so quickly, it's left many open source maintainers in a tough spot if they manage many npm packages or have multiple, legitimate publishing scripts they use to publish to npm, because currently projects can only designate one script as a project's Trusted Publishing script.
This one script issue was the problem I encountered with the npm package I maintain. There's two separate workflows with very different steps depending on whether I'm publishing a new production version of the library for public use, or a beta version of the library for the dev team to test against as they build new features.
I thought I would be stuck manually refreshing deprecated publishing tokens regularly to keep both workflows going, but lo and behold, the smart developers in the GitHub community clued me in to a feature I wasn't aware of in GitHub: GitHub Actions reusable workflows.
Through the power of reusable workflows, multiple GitHub Actions workflow scripts can be triggered to handle the different types of package publishing, while the reusable workflow file carries the necessary permissions to satisfy npm's trusted publishing (OIDC) requirements. Just what I needed.
Check back in a few weeks — I’ll be writing more about JavaScript, React, AI, or something else related to web development.
Thanks for reading, GitHub Action reusable workflows are not well documented as an option for those of us who have multiple GitHub Action workflow publishing files to publish to npm, but I hope this solution may work for you like it worked for me.
References & Further Resources
Want to be notified first when I publish new content? Subscribe to my newsletter.