npm-publish: a GitHub Action that publishes on a version change
GitHub Action to publish to NPM
At a glance
- What is it?
- The npm-publish action watches the version field in your package manifest and publishes to the registry when it differs from what is already there. It is written in TypeScript, it authenticates with trusted publishing over OIDC rather than a long lived token, and the same code is also usable as a function you call or as a command line tool. The readme's first section argues that you probably do not need it.
- Who is it for?
- npm-publish fits a project that bumps the version in its manifest as its release ritual, and that wants the publish to follow automatically without a tag and without a registry token in the repository. It does not fit a project that publishes on tags, or one that uses a different package manager, because the readme says plainly that the action is for one specific style of release and that the tag based route is both simpler and more secure.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 23 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The readme tells you to stop and read this
The most unusual thing about this project is that the second section of the readme, before the features, is a warning.
The heading says you probably do not need this, and the text explains why. The action automates a specific kind of continuous deployment to npm, where you publish whenever the version field in your manifest changes on your main branch. If you prefer to publish on tags, for example the ones created by the version command of the package manager, or you are using an alternative package manager, you do not need this action.
The alternative is given, and it is a complete workflow rather than a hint: configure the Node setup action with its registry option and call the publish command of your package manager directly. The readme then says that this is more secure than relying on a third party action like this one, and more customisable.
That claim is worth taking seriously rather than as marketing modesty. A third party action in a publish workflow is a dependency that runs with the credentials of the release, and the alternative the readme recommends is roughly ten lines of workflow that you own entirely. The action earns its place for the specific case where the version field is the trigger, and in that case it does the work that ten lines of workflow would otherwise have to get right, including the part where it decides whether a publish is needed at all.
So the first question for anyone evaluating it is not what it does but whether it matches your release ritual. If your release ritual is a tag, stop here and write the workflow yourself.
Five features, and one of them is the point
The feature list is short and each entry makes a specific claim.
The first is that the action is smart: it only publishes if the version number in your manifest differs from the latest one on npm. That is the behaviour that makes the whole thing safe to run on every push, because a push that does not change the version produces no publish, no tag, and no noise.
The second is that it is configurable: the version checking behaviour, the registry URL, and the path of the package are all settings rather than assumptions.
The third is security, and it is stated as two things: it keeps your npm authentication token secret, and it does not read or write to the npm configuration file in your home directory. The second half is the part that matters in continuous integration, where a stray write to that file is how a token ends up somewhere it should not be.
The fourth is speed, and the claim is that it is entirely JavaScript, which is faster than a Docker based action, and that it is bundled to optimise loading time. The loading time point is real, because an action that has to install a runtime before it can do anything adds seconds to every job that uses it.
The fifth is outputs: the old and new version numbers, and the type of the change, major, minor, patch, are exposed as variables that a workflow can use. That is what lets a publish step be followed by a changelog step or a notification, and it is the difference between an action that ends a job and an action that is one step in a pipeline.
The project is MIT licensed, and the release history is in the 4.1 line, with 4.1.5 in February 2026 after two more releases in January.
Three surfaces: the action, the function, the command
The usage section says the package can be used three different ways, and that is a real choice rather than a marketing list.
The first is as a GitHub Action, as part of a continuous delivery pipeline, which is the primary use and the one the rest of the readme is about.
The second is a function you call from your own JavaScript code, for the case where the decision to publish lives somewhere other than a workflow, such as a script that runs after a release or a monorepo tool that publishes several packages.
The third is a command line tool you run in your terminal, which is the one to reach for when you are publishing a single package from your own machine and want the same version check and the same behaviour the action would have given you.
The fact that all three are the same package is the point of the structure. The version comparison, the registry handling, and the publishing itself are one implementation, and the three surfaces are thin wrappers around it. That matters for the two non action surfaces, because a command line tool that behaves slightly differently from the action is a source of the kind of confusion where a package publishes in one place and not the other.
The repository layout supports that reading. There is an action manifest and an action entry point at the root for the action surface, a bin directory for the command line surface, a src directory with the implementation, and a lib directory for the built library that the function surface exports. The package manifest gives the library types and a default export pointing at the built index, and it registers the command under the name npm-publish.
The build is a TypeScript compile for the library and a separate bundling step for the action, the latter with tree shaking and a source map, and the two run together through a concurrent runner.
Trusted publishing, and the token you still need for private packages
The action's own documentation tells you what to configure, and there is one instruction that is easy to miss.
By default, the only thing you need to do is set the identity token permission to write, which enables trusted publishing over OIDC. The workflow shape is short: it runs on a push to the main branch, grants read access to the contents and write access to the identity token, checks out the code, sets up Node with a version, installs with the lock file, runs the tests, and then uses the action.
on:
push:
branches: main
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: "24"
- run: npm ci
- run: npm test
- uses: JS-DevTools/npm-publish@v4The permission is the whole authentication story in the common case. There is no token in the repository, and the registry trusts the workflow because of the identity token.
But the readme carries an important note about private packages. If you are publishing a private package with trusted publishing, you still need to provide a read only token, so that the action can read the existing versions from the registry before it publishes. The action needs to know what is already there in order to decide whether anything changed, and that read requires credentials that the identity token alone does not provide.
The note is presented as a diff on the action step, with the token coming from a repository secret, and the read only qualifier matters: the token is used to look, not to write.
The same section covers a different case, publishing to a third party registry such as the GitHub package registry, where the token is the repository token and the registry option points at that host, and the workflow permissions then need write access to packages.
The input table, and the strategy that changes the behaviour
The action takes a handful of inputs, and the table is worth reading because two of them change what the action does rather than where it publishes.
The token is the registry authentication token, and it is not required when using trusted publishing, which is the default path described above. The registry is the URL to use, defaulting to the npm registry. The package input is the path to a package directory, to a manifest, or to a packed archive to publish, and it defaults to the current working directory. The tag is the distribution tag, and it defaults to latest. The access input chooses whether the package is publicly visible or restricted, and it defaults to the registry's own defaults. Provenance is a boolean, off by default, and turning it on runs the publish with the provenance flag so that the registry records where the package came from. The ignore scripts input defaults to true, which runs the publish with the flag that stops lifecycle scripts from running.
That last default is a security decision rather than a convenience, and the readme explains it in the table. Lifecycle scripts in a published package run on the machine of anyone who installs it, so a package that is published from a pipeline should not be able to run arbitrary code as part of the publish step, and the default turns that off.
The strategy input is the one that changes the semantics. With all, the action publishes all unique versions it finds, and with upgrade it publishes only semver upgrades. That sounds like a small difference and is not, because publishing every unique version means a pipeline that has been re-run over a range of versions can push a sequence of releases to the registry, while the upgrade strategy only moves forward.
The ignore scripts default of true is worth checking against your own project, because a package whose build depends on a prepublish script will need it turned off, and that is a decision to make deliberately.
The package, the tests, and the publishing configuration
The manifest is a good description of the project's engineering.
It is an ES module package with type module, and it publishes three directories: the command line entry point, the built library, and the sources, with the test directory excluded explicitly. The engine requirement is Node 20 or newer.
The publishing configuration is the part with a consequence. The package is published with public access and with provenance enabled, which means the registry records a link between the published artefact and the source it was built from. For an action that publishes other people's packages, publishing with provenance is the behaviour you would want applied to itself, and it is a good demonstration that the option works.
The scripts show a project that treats its own build as a first class thing. There is an all script that cleans, builds, formats, and produces coverage in one go. The clean script removes the coverage, the library, the distribution, the end to end fixture directory, and any packed archive left behind. The build runs the two build steps concurrently, one of which compiles the library with the TypeScript compiler and the other bundles the action with a tree shaking bundler and a source map.
The test stack is vitest, with a coverage variant, and there is an end to end directory in the repository, with a fixture subdirectory that the clean script clears. The unit tests are run through the same runner.
The linting is eslint with a shared configuration from the same author, and a plugin for the documentation comments, and the formatting is prettier with its own shared configuration. Both are also run over the whole tree in the lint script, which checks the formatting as well as the linting.
The repository has the other files you would expect from a project with a public changelog and a contribution guide, and a 404 page in the repository root, which is a detail of the kind that suggests the project is also used to host documentation for its GitHub pages site.
Editorial conclusion
npm-publish fits a project that bumps the version in its manifest as its release ritual, and that wants the publish to follow automatically without a tag and without a registry token in the repository. It does not fit a project that publishes on tags, or one that uses a different package manager, because the readme says plainly that the action is for one specific style of release and that the tag based route is both simpler and more secure. Verify first that your registry supports trusted publishing, that the workflow grants the identity token permission, and that the strategy option matches what you want, because publishing every unique version and publishing only upgrades are different behaviours with different failure modes.
Frequently asked questions
What does the npm-publish GitHub Action do?
It publishes a package to npm whenever the version field in the package manifest differs from the latest version already on the registry. It can also be used as a function called from your own JavaScript, or as a command line tool, and it exposes the old and new version numbers and the type of change as workflow outputs.
When should I not use the npm-publish action?
If you publish on tags, for example the ones the version command creates, or if you use a different package manager. The readme recommends configuring the Node setup action with its registry option and calling the publish command of your package manager directly, which it says is more secure than relying on a third party action and more customisable.
How does the npm-publish action authenticate with the registry?
By default through trusted publishing over OIDC, which requires the workflow to set the identity token permission to write, so no token is stored in the repository. For a private package you still have to supply a read only token, so that the action can read the existing versions before it publishes.
What does the strategy option do in the npm-publish action?
With the default of all, the action publishes all unique versions it finds, and with upgrade it publishes only semver upgrades. The difference matters when a pipeline can re-run over a range of versions, because the first mode can push a sequence of releases to the registry while the second only moves forward.
What are the main inputs of the npm-publish action?
A registry token, the registry URL, the path of the package to publish, the distribution tag, the access level, whether to add provenance statements, the strategy for which versions to publish, and whether to run the publish with the ignore scripts flag, which defaults to true so that lifecycle scripts cannot run during the publish step.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/js-devtools-npm-publish)Was this useful?