How to Automate Your Changelog from GitHub Pull Requests

2026-09-05 · ShipChangelog

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

  1. Collect. On tag or deploy, fetch the PRs merged since the previous release: titles, labels, authors, and the BREAKING CHANGE footer when present.
  2. Group and summarize. Bucket PRs by label or commit type (features, fixes, breaking) and reword titles into reader-facing sentences.
  3. Review. A human approves the draft. This step stays; automation drafts, you decide.
  4. Publish. Hosted page, RSS, email digest, or a CHANGELOG.md commit — 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:

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.