Automated Publishing to NPM from GitHub

Nov 26, 2024

I’ve taken the time to automate the publishing process of my NPM packages, so that:

Here’s how I did it:


Create a new environment. Let’s call it “Publish” (some people may prefer calling it “Release”, but in my opinion, in the context of GitHub, “Release” is to put a release note on GitHub’s “Releases” page, while “npm publish” is the action we are trying to automate here):

screenshot of creating a new environment in GitHub repository settings screenshot of configuring the environment name

Set some protection rules:

screenshot of setting the protection rule that deployment requires reviewers

Log into https://npmjs.com and go to the “Access Tokens” page

screenshot of the npmjs.com dropdown menu, which includes an "Access Tokens" link

Generate a new Granular Access Token:

screenshot of generating access token from the npm page

Choose a name and type in the expiration date of the token:

screenshot: in the "General" section, the token name is set to "Publishing Token for MyAwesomePackage"; the expiration field is of "custom" type and the date is "31/12/2025"

In the “Package and Scopes” section, choose “Read and write” permissions, and select the one (or multiple) package(s) that you would publish from the repository.

screenshot of the aforementioned permission settings

Other fields can be safely ignored. Now click “Generate token”. Copy the generated token.


Go back to the Environment settings page on GitHub. In the “Environment secrets” section, click “Add environment secret” (note it’s not environment variable, but secret!)

Name it NPM_TOKEN and paste your newly generated token.

You might want some protections on the default branch too, if you are going to collaborate with other people and give them the write/release permission.

Go to “Settings” -> “Branches” -> “Add branch ruleset”.

Type in a name; make it active.

screenshot: ruleset name set to "No rewriting history on main" and enforecement status set to "Active"

Target the default branch:

screenshot: in the "Targets" section, the "Add target" button is clicked, a dropdown menu is shown, and the focus is on "Include default branch"

The default rules suffice for me. They prevent any user from deleting history from the default branch or forcing pushing to it.


Create a .github/workflows/publish.yml file in the project.

Typically, it may look like this, but may vary depending on the project:

name: Publish

on:
  push:
    tags:
      - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10

jobs:
  release:
    # Use Publish environment for deployment protection
    environment: Publish
    runs-on: ubuntu-latest
    permissions:
      contents: read
      id-token: write
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v3
        with:
          version: 9
      - uses: actions/setup-node@v4
        with:
          node-version: 'lts/*'
          registry-url: 'https://registry.npmjs.org'
      - run: pnpm install
      # Note: `--no-git-checks` is required due to https://github.com/pnpm/pnpm/issues/5894
      - run: pnpm publish --no-git-checks
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

Add "provenance": true to the package.json file under the publishConfig field.


Optionally, create a .github/MAINTENANCE.md file to explain the release process to other maintainers.

I use the following template for the maintenance document:

This document explains how to perform the project's maintenance tasks.

### Creating a new release

Anyone with write access to the repository can request a new release. To do so, follow these steps:

1. Run `pnpm version <patch|minor|major>` locally to bump the version number and create a new commit / tag.
2. Push the commit and tag to the repository by running `git push --follow-tags`.
3. The release will be automatically published to npm by GitHub Actions once approved by an administrator.
4. Go to <https://github.com/<OWNER>/<PROJECT>/releases/new> and create a new release with the tag that was just created. Describe the notable changes in the release notes.

I initially wanted to bump the version directly from the GitHub Actions. However, I later found no ideal way to sign the commit from GitHub Actions, especially when there’s a large commit object (such as in create-vue where I need to generate a large set of template snapshots). So I gave up and settled with the manual version bumping.

Some rights reserved
Except where otherwise noted, content on this page is licensed under a Creative Commons Attribution-ShareAlike 4.0 International license.