Automating publication prevents manual errors, speeds up releases, and forces you to maintain a repeatable and transparent process.
In this article, we're going to create a GitHub Actions workflow that publishes to NPM when you push to main
. The flow installs dependencies, runs tests, compiles, and, if all goes well, publishes the package using a secure token stored as a secret.
🎯 What is the purpose of the workflow?
Every time you merge changes to main
, we want to:
- Install dependencies
- Run tests
- Build the package
- Publish to NPM using secure credentials
This standardizes the publishing process and reduces the risk of human error.
🧬 General workflow structure
name: Publish to NPM
on:
push:
branches:
- main
jobs:
publish:
name: publish
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "22"
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm run test
- name: Build package
run: npm run build
- name: Publish to NPM
uses: JS-DevTools/npm-publish@v3
with:
token: ${{ secrets.NPM_TOKEN }}
🛠️ What does each step do?
on: push to main
Triggers the workflow when pushing to the main
branch. If you prefer to publish only with tags, you can change the trigger to on: push: tags: - ‘v*’
.
actions/checkout@v4
Downloads the repository to the runner.
actions/setup-node@v4
Configures Node.js (v22 in the example). You can enable cache: ‘npm’
to speed up installations.
npm ci / npm install
Installs dependencies. npm ci
is faster and more reproducible in CI if you use package-lock.json
.
npm run test and npm run build
Verify that the package passes tests and compiles correctly before publishing.
JS-DevTools/npm-publish@v3
Publish the package by reading package.json
. Use the token you pass through with.token
.
🔐 Authentication with NPM: NPM_TOKEN
To publish, you need a token in NPM and save it as a secret in GitHub:
- Create an Automation Token in your NPM account (recommended for CI; respects 2FA and allows automatic publishing).
- In GitHub, go to Settings → Secrets and variables → Actions → New repository secret.
- Create the secret
NPM_TOKEN
with the value of the NPM token.
💡 If your package is scoped (e.g.,
@your-org/package
), make sure thatpackage.json
contains“name”: “@your-org/package”
and“publishConfig”: { ‘access’: “public” }
if you want it to be public.
Example of publishConfig
in package.json
:
{
"name": "@tu-org/tu-paquete",
"version": "1.2.3",
"publishConfig": {
"access": "public",
"tag": "latest"
}
}
🧪 Dry-run and version control
Dry-run: test the flow without actually publishing:
- name: Publish to NPM (dry run)
uses: JS-DevTools/npm-publish@v3
with:
token: ${{ secrets.NPM_TOKEN }}
dry-run: true
Versioning: this flow publishes the version specified in package.json
. Be sure to update the version before merging to main
.
If you use automatic versioning (e.g., Conventional Commits + automatic releases), integrate that stage before the publication step.
✅ Final result
With this workflow:
- You publish to NPM in a consistent and repeatable manner.
- You avoid broken releases thanks to preliminary tests + builds.
- You keep credentials secure with GitHub Secrets.
- You reduce manual steps and time between merge and release.
🐙 Would you rather set it up with a visual interface?
If you're interested in defining this flow with a visual interface and getting the YAML instantly, you can use OctoLab:
- Step-by-step visual editor
- Dynamic fields for actions and tokens
- Real-time YAML preview
- Built-in validations
- Copy/download and you're done
👉 Try it out: https://www.octolab.app/templates/npm-publish
🧵 Conclusion
Automating publication to NPM with GitHub Actions reduces friction and errors. With a simple configuration—tests, build, and a publish step with a secure token—you can standardize releases and focus on building value.
If this was helpful, let me know what you would like to add to the template (tags, prereleases, monorepos, changelogs, etc.). Let's keep going!
Top comments (0)