INTERMEDIATEGITHUB-ACTIONSRELEASE MANAGEMENT

Create a GitHub Release When a Version Tag is Pushed

A GitHub Actions workflow that creates a GitHub Release with auto-generated notes whenever a v* tag is pushed.

By Tested on softprops/action-gh-release@v2, GitHub-hosted runners

A GitHub Actions workflow that automatically creates a GitHub Release whenever a tag matching v* is pushed. It uses GitHub's built-in release notes generator to summarize commits and merged PRs since the last tag. This is the OSS maintainer's bread-and-butter release automation.

Tested on softprops/action-gh-release@v2, GitHub-hosted runners.

When to Use This

  • Open source libraries with semantic versioning
  • Internal libraries published to npm/Maven Central
  • CLI tools where each tag corresponds to a binary release
  • Apps where each release needs human-readable notes

Don't use this when you do continuous deployment (every commit goes to prod โ€” no tags involved) or when releases must be manual (use workflow_dispatch instead).

Code

# .github/workflows/release.yml
name: Release
 
on:
  push:
    tags:
      - 'v*'
 
permissions:
  contents: write   # required to create releases
 
jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0   # full history for changelog generation
 
      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: '24'
          cache: 'npm'
 
      - name: Install dependencies
        run: npm ci
 
      - name: Build
        run: npm run build
 
      - name: Create GitHub Release
        uses: softprops/action-gh-release@v2
        with:
          generate_release_notes: true
          draft: false
          prerelease: ${{ contains(github.ref_name, '-') }}
          files: |
            dist/*.tar.gz
            dist/*.zip

The prerelease: ${{ contains(github.ref_name, '-') }} line marks any tag containing a hyphen (like v1.2.0-beta.1) as a pre-release automatically. This is the SemVer convention.

Usage

The workflow trigger is just a tag push:

# Make sure your work is committed and pushed
git push origin main
 
# Create and push the tag
git tag v1.2.0
git push origin v1.2.0

The workflow runs immediately. Within a minute, you have:

  1. A new release at github.com/<owner>/<repo>/releases/tag/v1.2.0
  2. Auto-generated release notes from PRs and commits
  3. Build artifacts attached if you specified files:

To customize the release notes grouping, add .github/release.yml:

# .github/release.yml
changelog:
  categories:
    - title: ๐Ÿš€ Features
      labels:
        - feature
        - enhancement
    - title: ๐Ÿ› Bug Fixes
      labels:
        - bug
        - fix
    - title: ๐Ÿ“š Docs
      labels:
        - documentation
    - title: ๐Ÿ—๏ธ Other
      labels:
        - "*"

PRs are now grouped by their labels in the release notes.

Pitfalls

  • permissions: contents: write is required. Without it, the workflow can't create releases. The default permissions are read-only on most repos.
  • fetch-depth: 0 is essential for changelog generation. With the default shallow clone, the action can't compute the diff between this tag and the previous one.
  • The tag must already exist when the workflow runs. This is a tag-trigger, so by definition the tag is there. But local tags don't trigger anything until pushed with git push origin v1.2.0.
  • Pre-release detection on hyphens is a SemVer convention, not a hard rule. v1.0.0-rc.1 is a pre-release, v1.0.0 is not. Customize the expression if your versioning differs.
  • softprops/action-gh-release@v2 is the de facto standard third-party action โ€” GitHub's official API works too, but the community action handles assets, drafts, and notes more cleanly.
  • Don't tag from forks. Tag triggers only fire on the upstream repo. If contributors create tags in forks, nothing happens.

Frequently Asked Questions

Why trigger releases on tag push instead of on commit?

Tags signal intent: 'this commit is a release'. Triggering on every commit creates noise and accidental releases. Tag triggers let you batch fixes and features into a coherent version, write proper release notes, and avoid releasing every WIP commit.

What does generate_release_notes do?

GitHub auto-generates release notes from the commit messages and merged PRs since the previous tag. You can customize the format with .github/release.yml, grouping by labels like 'bug', 'feature', or 'breaking'. The notes appear in the release body and the GitHub UI.

X (Twitter)LinkedIn