How to automate your changelog from GitHub pull requests
If your team ships through GitHub, your changelog already exists. It's sitting in your merged pull requests: what changed, when it merged, who did it, and usually why. The manual changelog ritual, where someone scrolls back through a fortnight of merges trying to remember what mattered, is re-deriving information the repo already holds.
Automating a changelog means building a pipeline from those merged PRs to a published page. This guide walks through that pipeline step by step. Most of it applies whatever tooling you use, including none.
What the pipeline looks like
Going from merged PRs to a published changelog takes five steps:
- Collect the PRs merged since your last update.
- Filter out the ones customers can't see.
- Categorise what's left: feature, fix, improvement, breaking.
- Rewrite each one in language a customer would understand.
- Review and publish.
Steps one to three are mechanical. Step four needs judgement about what a change means to the reader. Step five should always involve a human. The realistic goal of automation is to compress your involvement down to steps four and five, and ideally down to five alone, with four handed to you as a draft.
Make your PRs carry the signal
Every changelog automation, whether it's a script you wrote or a tool you bought, reads the same inputs: PR titles, labels, descriptions, and commit messages. A little hygiene here pays for itself immediately.
Use conventional-commit-style titles. feat:, fix:, and perf:
prefixes let a categoriser sort a PR without reading anything else.
feat!: or fix!: marks a breaking change. chore:, ci:, docs:,
test:, and deps: mark work that should never reach a customer's
eyes. If your team already writes titles this way, you're most of the
way to automatic categorisation.
Or use labels. If conventional commits never stuck, labels do the
same job: feature, bug, breaking-change, internal. Add a
skip-changelog label as an explicit escape hatch for anything you
never want published, whatever its title says.
Write two sentences of body. Not the template boilerplate with the unticked checkboxes. One or two sentences on what a user of the product would notice. A PR body that says "users can now export reports as CSV" gives any drafting step, human or machine, everything it needs. An empty body forces it to fall back on commit messages and guesswork.
Keep commit subjects meaningful. When a PR bundles several changes, the commit subjects are often the only record that they're distinct. "Fix pagination on the audit log" is signal. "wip" is not.
None of this is extra work invented for the changelog. The same hygiene helps reviewers today and the archaeologists of your own git history later.
Filter before you sort
A busy repo's merge history is mostly noise from a customer's point of view. Filter it early.
- Bot authors. Dependency-update PRs from the usual bots can dominate a week of merges and mean nothing to a customer. Exclude bot-authored PRs by default.
- Internal categories. Chores, CI changes, docs, tests, dependency bumps, and refactors with no user-visible effect. Real work, none of it an entry.
- The escape hatch. Honour a
skip-changeloglabel absolutely, so anyone on the team can pull a PR out of the pipeline with one click.
One subtlety worth building in: a breaking-change marker doesn't always
mean a customer-facing entry. A PR titled feat!: rename the accounts table breaks something internal, and nothing a customer can observe.
If the broken surface is a database schema or a private interface, it
belongs in the internal bucket, whatever the exclamation mark says.
Categorise with rules first, judgement second
Four customer-facing categories cover almost everything: feature, fix, improvement, and breaking. Plus internal, which is the bucket that never gets published.
Simple deterministic rules handle most PRs. A feat: prefix or a
feature label means feature. A fix: prefix or a bug label means
fix. A perf: prefix means improvement. There's no ambiguity to
resolve and no reason to involve anything cleverer.
The remainder, the PRs with a vague title and no labels, need actual judgement. That can be a human skimming the leftovers, or an LLM given the title, body, and labels. Either way, treat low-confidence categorisation as a flag for review rather than a silent decision. A fix published under "New features" reads worse than no entry at all.
One more case rules alone get wrong: a single PR that contains both a feature and a bug fix. Forcing it into one category buries one of the two changes. The better behaviour is one entry per change, each in its own section, even when they shipped in the same merge.
Draft in customer language
This is the step that scripts can't do, and the reason most home-grown changelog automation stalls at "a list of PR titles".
GitHub itself will auto-generate release notes from your merged PR
titles, and for an audience of developers reading your releases page,
that's often enough. But a customer-facing changelog needs the
translation: fix: guard null in useAuth hook on token refresh has to
become "fixed a bug where you could get logged out unexpectedly". The
title says what changed in the code. The entry has to say what changed
for the reader.
You have two options for this step. Do it by hand, working from the filtered, categorised list the earlier steps produced, which is already far faster than starting from a blank page. Or have an LLM draft it from the PR's title, body, and commit messages, and correct it where it's wrong. We've written separately about what makes the entries themselves worth reading; everything there applies whichever way you produce the first draft.
Review instead of writing
The end state of a well-automated changelog is that when you sit down to publish, the draft is already waiting. Your job is a review pass:
- Is each change in the right category?
- Does each entry describe what the reader experiences, with the implementation details gone?
- Did anything slip through that shouldn't be public yet: an unannounced feature, a security fix you're coordinating disclosure on?
- Is anything missing that customers will ask about?
That's a few minutes of reading, and it's a job that fits the Friday afternoon it always lands on. The review also stays worth doing. Automation is good at the mechanical steps and decent at the drafting, and it will still occasionally miscategorise something or miss the point of a change. Keep the human as the editor, and keep publishing as a deliberate act rather than a side effect of merging.
Where ChangelogPilot fits
ChangelogPilot is this pipeline as a product. Connect a GitHub repo and
it reads your merged PRs, skips bot-authored and internal work
(including anything labelled skip-changelog), and categorises each
change as a feature, fix, improvement, or breaking change. A PR that
contains both a feature and a fix produces an entry in each section.
Drafts are written in plain customer language from the PR's title,
body, labels, and commit messages, and anything the drafter is unsure
about arrives flagged for your attention. You review, edit if you
like, approve, and it publishes to a hosted changelog page. You can
also choose the voice the drafts are written in, from plain reporting
to more benefit-led framing.
If you'd rather build the pipeline yourself, everything above is enough to start with, and the PR hygiene section will make whatever you build work better. If you'd rather just do the review pass, sign in, connect a repo, and see what it drafts from your last few weeks of merges.