How to automate release notes on GitHub
Every real way to generate release notes on GitHub: the built-in automatic generator and its .github/release.yml config, the GitHub CLI flag, the REST API endpoint, a GitHub Actions workflow, and the third-party tools worth knowing - release-please, semantic-release, git-cliff, changesets, and release-drafter.
What this solves
A maintainer wants release notes to stop being manual work: they are looking for GitHub's own generator, the CLI or API way to trigger it, a CI recipe, and how the popular third-party generators compare.
How S2P helps
Every real way to get release notes out of a GitHub repo automatically, from the zero-config built-in generator to a full GitHub Actions pipeline, plus an honest map of the third-party tools and when each one earns its setup cost.
Key takeaways
- GitHub's automatic release notes need zero configuration; a .github/release.yml file only customizes the categories.
- The same generator is reachable three ways: the release UI, gh release create --generate-notes, and the REST API's generate-notes endpoint.
- A tag-push GitHub Actions workflow can create a fully-noted release with no third-party tool at all.
- Conventional-commit tools (release-please, semantic-release, git-cliff) trade a commit-message convention for automatic versioning; GitHub's own generator trades that convention for working from pull requests instead.
Section 1
GitHub's built-in automatically generated release notes
GitHub can write your release notes for you with zero configuration, by turning merged pull requests since your last release into a formatted summary.
GitHub's automatically generated release notes are a built-in alternative to typing release notes by hand. Given a tag, GitHub generates a list of merged pull requests since the previous release, a list of contributors, and a link to the full diff (a 'full changelog' comparison link). This works the moment you tag a release; there is no setup requirement, because the source of truth is your pull request history, not a commit-message format.
The only optional step is a `.github/release.yml` file, which controls how pull requests get grouped into categories and which ones get left out entirely. Without it, GitHub still generates notes - they just are not split into sections like 'Breaking changes' or 'Bug fixes'. With it, every merged pull request is sorted by label into the categories you define, and specific labels or authors (for example a bot that opens dependency-bump PRs) can be excluded so they do not clutter the notes.
Here is a complete, valid example. `changelog.exclude` applies globally; each entry under `categories` groups pull requests carrying any of its `labels`, and a lone `*` in a category's `labels` list acts as a catch-all for anything not claimed by an earlier category.
- Works with zero config: tag a release and GitHub drafts notes from merged pull requests.
- `.github/release.yml` is optional and only controls categorization, not whether notes generate.
- `changelog.exclude.labels` and `changelog.exclude.authors` drop noise (bot PRs, internal-only changes) globally.
- A `"*"` label inside a category acts as a catch-all for everything not already categorized.
.github/release.yml
changelog:
exclude:
labels:
- ignore-for-release
authors:
- dependabot
- octocat
categories:
- title: Breaking Changes
labels:
- breaking-change
- title: New Features
labels:
- enhancement
- feature
- title: Bug Fixes
labels:
- bug
- title: Other Changes
labels:
- "*"Section 2
Generate release notes with the GitHub CLI
If you already tag and publish releases from a terminal or a script, the CLI reaches the same generator with one flag.
`gh release create` accepts a `--generate-notes` flag: 'Automatically generate title and notes for the release'. It calls the same generator described above, so it respects `.github/release.yml` if one exists, and needs nothing else configured. This is the fastest path if you already have a release process built around the GitHub CLI, or you are scripting a release step outside of Actions.
- `--generate-notes` is a flag on `gh release create`, no other setup required.
- Uses the same generator as the release UI and honors `.github/release.yml`.
- Combine it with `--notes` to append manual notes above the generated section.
- Works equally well from a laptop or from inside CI.
Create a release with generated notes
gh release create v1.4.0 \
--title "v1.4.0" \
--generate-notesSection 3
Generate release notes with the REST API
For anything the CLI cannot script directly - a custom release dashboard, a bot, a non-GitHub-Actions pipeline - the same generator is a single REST call.
The REST API's 'Generate release notes content for a release' endpoint is `POST /repos/{owner}/{repo}/releases/generate-notes`. Given a `tag_name` (and, optionally, `target_commitish` and `previous_tag_name` to control the comparison range), it returns a generated `name` and markdown `body` without creating or saving anything - you decide what to do with the result, including passing it straight into a subsequent create-release call.
- Endpoint: `POST /repos/{owner}/{repo}/releases/generate-notes`.
- Requires only `tag_name`; `target_commitish` and `previous_tag_name` are optional overrides.
- Returns a `name` and markdown `body` - it does not create the release for you.
- Use `Accept: application/vnd.github+json`, the header GitHub's REST API examples use.
Generate notes for a tag via curl
curl -L \
-X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer <YOUR-TOKEN>" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/OWNER/REPO/releases/generate-notes \
-d '{"tag_name":"v1.4.0"}'Section 4
Automate it with a GitHub Actions workflow
Put the CLI flag from above inside a tag-triggered workflow and a fully-noted release appears with zero manual steps, no third-party action required.
`gh` is preinstalled on GitHub-hosted runners, so the same `--generate-notes` flag from the CLI section works directly inside a workflow step. The workflow below fires on any `vX.Y.Z` tag push, checks out the repo, and creates the release with generated notes using the workflow's own `GITHUB_TOKEN`. `permissions: contents: write` is required for `gh release create` to succeed with the default token.
- Triggers on tag push, matching semver-style tags like v1.4.0.
- No third-party action needed - `gh` ships on GitHub-hosted runners.
- `permissions: contents: write` is required for the default token to create a release.
- `github.ref_name` gives you the tag name that triggered the run.
.github/workflows/release.yml
name: Release
on:
push:
tags:
- "v*.*.*"
permissions:
contents: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Create release with generated notes
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release create "${{ github.ref_name }}" \
--title "${{ github.ref_name }}" \
--generate-notesSection 5
Third-party changelog generators, and what they actually expect
GitHub's own generator works from pull requests. The tools below work from commit messages or explicit change files instead, and each one trades a convention for automatic versioning.
`googleapis/release-please` automates changelog generation, GitHub release creation, and version bumps by watching your default branch for Conventional Commits (`feat:`, `fix:`, `feat!:` for breaking changes). Instead of releasing on every merge, it keeps an up-to-date 'release PR' that accumulates changes; merging that PR is what tags the release and publishes it.
`semantic-release/semantic-release` automates the entire release workflow - version number, release notes, and publishing - by analyzing commit messages against the Angular commit convention by default (`fix:` for patch, `feat:` for minor, a `BREAKING CHANGE:` footer for major). It is designed to run unattended in CI after a successful build, with no human choosing the version number.
`orhun/git-cliff` generates a changelog file from git history, primarily by parsing Conventional Commits, with configurable regex-based parsers for repos that use a different message format. Output is controlled through a TOML config, which makes it a good fit for teams that want a self-hosted changelog file rather than a hosted release page.
`changesets/changesets` takes a different approach built for monorepos: instead of parsing commit messages, contributors add a small changeset file declaring which packages changed and what semver bump each needs. `@changesets/cli` aggregates those files, bumps versions, updates changelogs, and handles internal package interdependencies - no conventional-commit discipline required.
`release-drafter/release-drafter` is closer to GitHub's own generator than the others: it drafts release notes as pull requests merge, grouping them by label into categories you define in `.github/release-drafter.yml`, and it can auto-calculate the next semantic version from those labels. It does not require conventional commits at all - it is a label-driven alternative to GitHub's native generator with more template control.
- release-please and semantic-release both require Conventional Commits; they differ in whether a release PR gathers changes first (release-please) or CI ships on every qualifying commit (semantic-release).
- git-cliff generates a changelog file from commit history and works best with Conventional Commits, though custom regex parsers exist for other formats.
- changesets skips commit-message parsing entirely in favor of explicit per-change files, which is why monorepos favor it.
- release-drafter is label-driven like GitHub's native generator, but adds configurable templates and version calculation.
Section 6
Comparing your options
Same underlying question - where do the words in the release come from - answered eight different ways.
Read this top to bottom as increasing setup cost for increasing control. The first three rows are the same GitHub-native generator reached through three interfaces; nothing below them requires you to leave your pull-request workflow if you do not want to.
Release notes methods compared
| Method | Where notes come from | Config required | Needs conventional commits | Runs in CI |
|---|---|---|---|---|
| GitHub native (release UI) | Merged pull requests since last release | Optional (.github/release.yml) | No | N/A, manual trigger |
| gh release create --generate-notes | Same generator, via CLI | Optional (.github/release.yml) | No | Yes, easy to script |
| REST API generate-notes | Same generator, via API call | Optional (.github/release.yml) | No | Yes |
| release-please | Conventional Commit messages | release-please config + manifest | Yes | Yes |
| semantic-release | Angular-convention commit messages | Plugin config in package.json | Yes | Yes, unattended |
| git-cliff | Git history, conventional or custom regex | cliff.toml | Recommended, not required | Yes |
| changesets | Changeset files added per change | .changeset config | No | Yes |
| release-drafter | Merged pull requests, grouped by label | .github/release-drafter.yml | No | Yes |
Section 7
The honest limit: generated notes aren't an announcement
Every method above produces notes written for people already reading your repo. That is a different job from telling people who are not.
Generated release notes - GitHub's or any of the tools above - are optimized for a specific reader: someone who already knows the project, is comparing versions, and wants a precise technical record. A bulleted list of merged pull requests, correctly categorized, does that job well. It does not do a second job it was never built for: giving someone who does not read your repo a reason to notice you shipped.
- Generated notes assume the reader already has context on the project; an announcement has to supply it.
- A pull-request title is written for a reviewer, not for a LinkedIn or X audience discovering the project cold.
- Turning a changelog into content for people outside the repo is a distinct, separate job.
- See our full guide on turning a changelog into content for that half of the problem.
FAQ
Questions this article answers
How do I turn on GitHub's automatic release notes?
You do not have to turn it on. When you create a release from a tag, GitHub's release UI, `gh release create --generate-notes`, or the REST API's generate-notes endpoint will all generate notes from merged pull requests since the previous release with no configuration. A `.github/release.yml` file is optional and only controls how those pull requests are categorized.
What does .github/release.yml control?
It controls categorization only. Under `changelog.categories`, each entry has a `title` and a list of `labels`; pull requests carrying those labels land under that heading, and a `"*"` label acts as a catch-all. `changelog.exclude.labels` and `changelog.exclude.authors` remove pull requests or authors (like a dependency-bump bot) from the notes entirely.
How do I generate release notes with the GitHub CLI?
Run `gh release create <tag> --generate-notes`. The flag automatically generates the title and notes via the same generator the release UI uses, and it still respects `.github/release.yml` if your repo has one. No other setup is required.
Is there a REST API for generating release notes?
Yes: `POST /repos/{owner}/{repo}/releases/generate-notes` with a `tag_name` in the request body returns a generated `name` and markdown `body`. It does not create or save a release itself; you take the returned content and use it however you need, including feeding it into a subsequent create-release call.
Do I need conventional commits for GitHub's automatic release notes?
No. GitHub's native generator works from merged pull requests, not commit message format, so it has no commit-convention requirement at all. Conventional Commits matter for a different category of tool - release-please, semantic-release, and (recommended but not required) git-cliff - which parse commit messages to decide the next version number.
What's the difference between release-please and semantic-release?
Both read Conventional Commits to determine versioning and release notes. release-please accumulates changes into an open 'release PR' that you merge when ready, which gives you a review step before anything ships. semantic-release is built to run unattended in CI and ships a release automatically whenever a qualifying commit lands, with no human choosing the version.
Can I auto-create a GitHub release when I push a tag?
Yes, with a short GitHub Actions workflow: trigger on `push: tags`, check out the repo, and run `gh release create "${{ github.ref_name }}" --generate-notes` with `permissions: contents: write` set. No third-party action is required because `gh` is preinstalled on GitHub-hosted runners.
Related guides and pages
Where to go next
Hand-picked pages that go deeper on the workflow, channels, and tooling covered above.
