- Published on ·
- Reading time 6 min read
The Magic of GitHub Actions
Publishing the same code base to two different npm registries
Share this page

Introduction
If you're developing an npm package, you might want to publish the same piece of code to multiple npm registries to potentially cater to a broader audience. Let's say we decide to publish our npm package to npm js and GitHub. Although this may seem straightforward, it has one little quirk we need to get around. Let's take a look!
Getting the GitHub Action ready
We have our npm package in this GitHub repository so naturally we'll be using GitHub Actions to build and publish our npm package. You can view the entire workflow file here, but let me break it down for you in this article.
Building the npm package
The steps outlined in the code snippet below are responsible for checking out the code from the repository, installing the dependencies, running the required tools to ensure consistency in coding standards, running unit tests to ensure new changes don't break the project, and finally building the project to compile TypeScript into the JavaScript output.
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Install Node.js
uses: actions/setup-node@v1
with:
node-version: 20.x
- name: Install dependencies
run: npm ci
- name: Run lint
run: npm run lint
- name: Run prettier
run: npm run pretty
- name: Run tests
run: npm run test
- name: Build package
run: npm run build
These set of steps should be pretty standard if you're working on multiple TypeScript projects.
Publishing the npm package
Once the build steps are done, we primarily add two more steps to publish the npm package — one to the npm js registry and the other to the GitHub registry — which is shown in the code snippet below. The benefit of using GitHub Actions is that you don't have to write all the commands yourself. We can use Actions from the GitHub marketplace that have been developed and published by other developers from the community, simply supply a few parameters, and voila!
steps:
- name: Publish package to npm
id: publish
uses: JS-DevTools/npm-publish@v2
with:
token: ${{ secrets.NPM_TOKEN }}
- name: Publish package to GitHub
id: publishgithub
uses: JS-DevTools/npm-publish@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
registry: 'https://npm.pkg.github.com'
The JS-DevTools/npm-publish Action encapsulates all the underlying commands to publish the package not only to the npm js registry but also to other third-party registries that implement the CommonJS Package Registry specification.
For the publish to npm step, we'll need to head to the access tokens page in npm js and create a granular token with write access. We'll then add this token value as a GitHub secret with the key NPM_TOKEN
so this value can be fetched from the GitHub Action using the syntax ${{ secrets.NPM_TOKEN }}
and supplied to our step.
Tip: While creating a granular npm access token, you can limit the scope of the token to only one package so that your token can only write to one package. But until you publish the package, it won't show up in the options to choose from. I've gotten around this by publishing a beta version of the package to npm js via the command line just before creating the token so that when I do create the token, I can limit its scope to just this package.
For the publish to GitHub step, we'll need to supply it with ${{ secrets.GITHUB_TOKEN }}
which is a built-in token that's made available by GitHub for such use cases. I've written a bit more about this in this article, so feel free to add it to your reading list.
Running the GitHub Action
Now that we're done writing the GitHub Action workflow file, it's time to run it and see the magic happen. You can do this simply by pushing a new change to your GitHub repository, something small like even bumping the version in the package.json file, and this will trigger the GitHub Action.
But wait, if we take a look at the logs, we see that while the publishing to npm step has succeeded, the publish to GitHub step has failed. Why is that?

Image courtesy of the author
If you've followed the article I linked above, then certainly the problem shouldn't be related to the permissions that the GitHub token has. After some reading, I found this answer that basically says that the scope of the package should match the GitHub username or the GitHub organization name that this package is being published to.
My GitHub username is clydedz and the scope I was using for the npm package is @clydedsouza. All I needed was to update this replacing @clydedsouza/horoscope
with @clydedz/horoscope
, as shown in the code snippet below.
This all seems straightforward, but I cannot update the package.json file in the repository because then I'd have the opposite problem — my npm js username is clydedsouza and if I try to publish a package with the scope clydedz, npm js will disallow.
Let's bring an Action to the rescue
There might be a few ways to solve this problem and if you can think of one, feel free to drop them in the comments below, but for this article, I'll share the solution that I ended up implementing.
After I published the npm package to the npm js registry, I added a new action called restackio/update-json-values-action. This action allows me to replace values from a JSON file, and as you might've already suspected, I'm replacing @clydedsouza/horoscope with @clydedz/horoscope in the package.json file. This change is also shown in the code snippet below.
steps:
- name: Publish package to npm
id: publish
uses: JS-DevTools/npm-publish@v2
with:
token: ${{ secrets.NPM_TOKEN }}
- name: Update scope name in package json file for GitHub
uses: restackio/update-json-values-[email protected]
with:
file: package.json
values: '{"@clydedsouza/horoscope": "@clydedz/horoscope"}'
- name: Publish package to GitHub
id: publishgithub
uses: JS-DevTools/npm-publish@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
registry: 'https://npm.pkg.github.com'
This doesn't create a new file but just overwrites the existing package.json file in the workflow working directory. It doesn't commit the updated file to the git repository either. When the next step to publish the package to GitHub is executed, it uses this updated package.json file with the correct scope. Now when I run the Action again, it passes and publishes the package to the GitHub registry successfully.

Image courtesy of the author
The full source code for the project used in this article can be found here on GitHub. If you have any questions, please feel free to comment down below.
That's it! Thanks for reading.