← All posts
· 4 min read ·
PythonGitCI/CDAutomationOpen Source

release-notes-generator: Structured Release Notes from Conventional Commits

A Python CLI that parses Conventional Commits history and generates Markdown, plain text, or JSON release notes - with GitHub Actions integration to auto-populate GitHub Releases on every tag.

Code editor on a monitor showing a structured changelog file

Release notes are one of those things that every team agrees they should write and almost nobody writes consistently. The manual process is: look at the commits since the last release, figure out what’s user-facing, write a summary, hope you didn’t miss anything. It takes 20–30 minutes and the result quality is inconsistent.

release-notes-generator automates this if your team uses Conventional Commits. Given a commit range, it produces structured release notes in Markdown, plain text, or JSON - and plugs into GitHub Actions to automatically populate the release body on every tag push.

Conventional Commits

Conventional Commits is a lightweight specification for commit messages:

<type>(<scope>): <description>

[optional body]

[optional footer(s)]

Common types:

  • feat - new feature (maps to “What’s New” in release notes)
  • fix - bug fix (maps to “Bug Fixes”)
  • perf - performance improvement
  • refactor - non-functional code change
  • docs - documentation
  • chore - build tooling, dependencies
  • BREAKING CHANGE - breaking change (triggers major version bump)

A commit history that follows this convention is machine-readable. The generator parses it and groups entries by type.

Output

Given this commit history:

feat(orders): add bulk cancellation endpoint
fix(auth): resolve token expiry edge case on mobile Safari
feat(inventory): real-time stock level updates via Platform Events
fix(checkout): correct VAT calculation for EU addresses
perf(search): cache product catalogue queries in Redis
chore(deps): bump lodash to 4.17.21

The generator produces:

## v2.4.0 - 17 February 2026

### What's New
- **orders**: add bulk cancellation endpoint
- **inventory**: real-time stock level updates via Platform Events

### Performance
- **search**: cache product catalogue queries in Redis

### Bug Fixes
- **auth**: resolve token expiry edge case on mobile Safari
- **checkout**: correct VAT calculation for EU addresses

Dependency bumps and non-user-facing changes (chore, refactor, docs) are excluded by default. You can include them with --include-all.

CLI Usage

pip install release-notes-generator

# Since last tag
release-notes generate

# Between specific refs
release-notes generate --from v2.3.0 --to v2.4.0

# Output formats
release-notes generate --format markdown   # default
release-notes generate --format text
release-notes generate --format json

# Write to file
release-notes generate --output CHANGELOG.md

# Include all commit types
release-notes generate --include-all

# Include commit hashes in output
release-notes generate --show-hashes

GitHub Actions Integration

The primary integration point is a workflow that fires on tag pushes and creates (or updates) the GitHub Release with the generated notes:

name: Release

on:
  push:
    tags:
      - 'v*'

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0  # required for full git history

      - name: Generate release notes
        run: |
          pip install release-notes-generator
          release-notes generate \
            --from $(git describe --tags --abbrev=0 HEAD^) \
            --to ${{ github.ref_name }} \
            --format markdown \
            --output notes.md

      - name: Create GitHub Release
        uses: softprops/action-gh-release@v1
        with:
          body_path: notes.md
          draft: false

fetch-depth: 0 is critical - without it, the shallow clone only has one commit and there’s no history to parse.

JSON Format for Custom Pipelines

The JSON output is useful when you need to feed release notes into a custom process - a Jira release, a Confluence page, a Slack message formatter:

{
  "version": "v2.4.0",
  "date": "2026-02-17",
  "sections": [
    {
      "type": "feat",
      "label": "What's New",
      "entries": [
        { "scope": "orders", "description": "add bulk cancellation endpoint", "hash": "a1b2c3d" },
        { "scope": "inventory", "description": "real-time stock level updates via Platform Events", "hash": "e4f5g6h" }
      ]
    },
    {
      "type": "fix",
      "label": "Bug Fixes",
      "entries": [...]
    }
  ]
}

Handling Non-Conventional Commits

Not every commit in a real repository follows the convention - merge commits, automated dependency bumps, and commits from developers who haven’t adopted the format yet all appear in the history.

The generator handles this gracefully:

  • Non-conventional commits are grouped under “Other Changes” (hidden by default)
  • Merge commits are always excluded
  • Reverts are detected by the revert: prefix and excluded from the notes (they indicate a change that was undone)

Versioning Integration

The tool optionally integrates with bump2version or python-semantic-release to determine the new version number from the commit types:

  • Any BREAKING CHANGE footer → major bump
  • Any feat commit → minor bump
  • Only fix, perf, docs, chore → patch bump
release-notes version  # prints calculated next version

This makes it possible to fully automate versioning without manual tag creation.

The project is on GitHub.

← All posts