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.
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/*.zipThe 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.0The workflow runs immediately. Within a minute, you have:
- A new release at
github.com/<owner>/<repo>/releases/tag/v1.2.0 - Auto-generated release notes from PRs and commits
- 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: writeis required. Without it, the workflow can't create releases. The default permissions are read-only on most repos.fetch-depth: 0is 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.1is a pre-release,v1.0.0is not. Customize the expression if your versioning differs. softprops/action-gh-release@v2is 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.
Related Snippets & Reading
- Next.js CI Workflow โ runs before this on every push
- Reusable Workflow with workflow_call โ share the build step across CI and release
- softprops/action-gh-release โ official action docs
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.