Overview
A GitHub Release is a named snapshot of the repository at a tag, with a changelog and optional binary assets. Done well, releases give users and automation a stable reference point and a human-readable record of what changed. Done poorly, releases are a sporadic collection of “v2 final FINAL” tags with empty bodies. This page covers the tag convention, semver rules, auto-generated changelogs, and the draft-then-publish flow. Read github-actions for the release.yml workflow that automates this.
Tag releases from main using semver
Every release tag points to a commit on main. The tag format is vMAJOR.MINOR.PATCH (semver) with no variation: no ver, no release-, no trailing -stable.
MAJORincrements on breaking changes: removed APIs, changed behavior that requires user action.MINORincrements on new backward-compatible features.PATCHincrements on backward-compatible bug fixes.
For content sites and documentation, treat a major restructure (changed URLs, removed pages) as a MAJOR bump, a new section as MINOR, and copy fixes as PATCH. The version is a contract with any consumer that links to or depends on your output.
Pre-release versions use the format v1.2.0-beta.1. Use pre-releases to publish a release for testing before it graduates to the stable channel.
Draft the release before publishing
Create a draft release so you can review the generated notes before the release is visible to users. A draft is not visible on the releases page and does not notify watchers.
# Create a draft release with auto-generated notes
gh release create v1.4.0 --draft \
--title "v1.4.0" \
--generate-notes
# Edit, then publish
gh release edit v1.4.0 --draft=falseThe draft window is the time to add context the auto-generator misses: migration notes, linked issues, and known limitations. Publish only when the notes are accurate and complete.
Use auto-generated changelogs from PR titles
GitHub can generate a changelog from the PR titles merged since the previous release. The output is grouped by Conventional Commits type if the PR titles follow the convention from github-pr-workflow.
Configure the grouping in .github/release.yml (separate from the Actions workflow file):
changelog:
categories:
- title: "Features"
labels:
- "enhancement"
- title: "Bug Fixes"
labels:
- "bug"
- title: "Maintenance"
labels:
- "chore"
- "dependencies"
- title: "Other Changes"
labels: ["*"]Apply labels to PRs at open time, or let the auto-labeler apply them based on PR title prefixes. The generated changelog is as good as the PR titles and labels; this is why github-pr-workflow enforces Conventional Commits.
Automate tagging and release creation in release.yml
A release.yml workflow triggered on push to main or on manual dispatch can create the tag and draft release without human intervention.
on:
workflow_dispatch:
inputs:
version:
description: "Semver version (e.g. 1.4.0)"
required: true
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- name: Create tag and draft release
run: |
git tag "v${{ github.event.inputs.version }}"
git push origin "v${{ github.event.inputs.version }}"
gh release create "v${{ github.event.inputs.version }}" \
--draft --generate-notes \
--title "v${{ github.event.inputs.version }}"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}Manual dispatch keeps the human in the loop for the version number decision. A fully automated release that bumps based on commit types is possible but requires a strategy for handling MAJOR bumps automatically, which is rarely worth the complexity.
Attach build artifacts to the release
Binary releases (compiled executables, bundled packages, exported PDFs) attach as assets to the release object.
gh release upload v1.4.0 dist/myapp-linux-amd64 dist/myapp-darwin-arm64Name artifacts with the version and platform: myapp-v1.4.0-linux-amd64. Consistent naming lets automation download the right asset without parsing the release notes.
For static site deployments, the public/ directory is not a release artifact; the deploy workflow handles it. See deploy-quartz-site and github-pages for the deploy path.
Write release notes that answer “what do I need to do?”
Auto-generated notes list what merged; the human-written introduction answers why it matters and what action users should take.
A useful release note structure:
- One sentence on the theme of this release (“This release adds OIDC support and removes the legacy token flow.“)
- Migration notes if anything breaks or requires user action.
- Auto-generated changelog below the fold.
Keep migration notes above the generated list so readers see them first. A changelog that buries “you must rotate your API key” in the middle of thirty merged PRs ships breakage silently.