How to Automate Your Changelog from GitHub Pull Requests
The short version: point something at your repo, let it collect the PRs merged since your last release, turn them into a draft entry, and publish that entry on deploy. Everything in this article is one variation of that pipeline; the difference is how much of it you build yourself.
Why the manual changelog breaks down
A manual changelog dies from friction, not from lack of care. The steps that used to take two minutes — open the PR list, summarize, paste, format — grow to twenty as the PR list grows, and the busiest person on the team is exactly the person who skips the step. By release five, the changelog is a museum piece.
Automation works because it moves the step into the pipeline: the release event already happens, so the changelog entry happens with it.
The pipeline in four steps
- Collect. On tag or deploy, fetch the PRs merged since the previous release: titles, labels, authors, and the
BREAKING CHANGEfooter when present. - Group and summarize. Bucket PRs by label or commit type (features, fixes, breaking) and reword titles into reader-facing sentences.
- Review. A human approves the draft. This step stays; automation drafts, you decide.
- Publish. Hosted page, RSS, email digest, or a
CHANGELOG.mdcommit — whichever your readers actually check.
Option 1: DIY with GitHub Actions
If you want the file in your repo, the standard DIY is a workflow that drafts release notes when a tag is pushed. A minimal version pulls merged PRs from the GitHub API:
# .github/workflows/changelog.yml
name: changelog
on:
push:
tags: ["v*"]
jobs:
draft:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Build notes from merged PRs
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
curl -s "https://api.github.com/repos/${{ github.repository }}/pulls?state=closed&per_page=100" \
| jq -r '.[] | select(.merged_at != null) | "- \(.title) (#\(.number))"' \
> CHANGELOG.draft.md
GitHub's release-drafter action is a ready-made upgrade of this pattern: it collects PRs by label into categorized release notes. Two honest caveats apply to both. First, raw PR titles are a "list of PRs" — readable to your team, not to your users; you need a summarization or rewording step to close that gap. Second, this writes a markdown file; nobody sees it until someone commits it somewhere public. A fuller version, with conventional-commits parsing, is covered in how to generate a changelog from conventional commits.
Option 2: Hosted services
The alternative to CI is a service that watches the repo for you. AutoChangelog triggers on deploys from GitHub; Beamer and LaunchNotes embed the changelog in broader feedback workflows; ShipChangelog watches merged PRs (or a CI webhook) and drafts entries you can edit before publishing, with breaking changes flagged. The trade-off runs in both directions: DIY keeps the text in your repo and your pipeline; hosted services keep it on a public page your users can find, with RSS and email included.
Rules that keep the output readable
Whichever route you take, these four conventions decide whether the result reads like a changelog or like a merge queue:
- Write PR titles as user-facing sentences: "Add dark mode to dashboard", not "Dark mode thing".
- Use labels (
feature,fix,breaking) so grouping is mechanical. - Mark breaking changes with a
BREAKING CHANGEfooter or label — that is the signal every detector reads. - Keep the review step. One minute of editing converts PR soup into an entry.
ShipChangelog
If you want the hosted-page version of this pipeline without writing it yourself, ShipChangelog connects to your GitHub repo or CI webhook and runs the collect–summarize–review–publish loop: merged PRs become draft entries, BREAKING CHANGE markers and major-version bumps are highlighted, and one release produces developer, customer, and stakeholder versions. Free for one repo with 5 entries a month.
FAQ
Do I need conventional commits to automate from PRs?
No. PR-based automation reads PR titles and labels, which most teams already have even without strict commit conventions. Conventional commits matter more for commit-based tools like git-cliff; see the conventional commits guide if that is your setup.
How do I handle releases with no user-facing changes?
Skip the entry or write a one-liner such as "Maintenance release: dependency updates and internal refactors." A good pipeline makes skipping easy; a release with zero user-visible PRs should not force a fake entry.
Will automation fill my changelog with junk?
Only if you let every internal PR through. The fix is the same as for any filter: labels for user-facing work, a breaking-change convention for risky ones, and a human review step that can delete lines.
ShipChangelog writes these changelogs for you — start free with 1 repo.