Conventional Commits and git-cliff: Automated WordPress Plugin Changelogs

Conventional Commits is a specification for writing structured commit messages — feat:, fix:, chore: — that enables tools like git-cliff to automatically generate a categorised CHANGELOG from your Git history. This is standard practice for maintained WordPress plugins.

Problem: WordPress plugin releases are documented inconsistently — commit messages use free-form text, the changelog in readme.txt is updated manually, and there is no automated way to generate release notes from the commit history.

Solution: Adopt Conventional Commits format (feat:, fix:, chore:) enforced by a commit-msg Git hook, then use git-cliff to auto-generate a changelog from the commit history. Configure cliff.toml to group by type and filter out chore commits, then run it in a GitHub Actions release workflow to publish the changelog automatically.

The examples below configure Conventional Commits with a Git hook, set up git-cliff to generate a WordPress-plugin-style CHANGELOG, and add a GitHub Actions step that auto-generates release notes on each tag push.

# Conventional Commit format:
# [optional scope]: 
# [optional body]
# [optional footer(s)]

# Types:
# feat:     New feature (bumps MINOR)
# fix:      Bug fix (bumps PATCH)
# docs:     Documentation only
# style:    Formatting, no logic change
# refactor: Code change, neither fix nor feat
# perf:     Performance improvement
# test:     Adding or updating tests
# chore:    Build process, dependencies
# BREAKING CHANGE: (in footer or ! after type) bumps MAJOR

# Examples:
git commit -m "feat(cart): add quantity stepper to cart line items"
git commit -m "fix(checkout): correct nonce validation on order submit"
git commit -m "feat!: drop support for PHP 7.4"
# The ! signals a BREAKING CHANGE → major version bump

# Install git-cliff (Rust-based changelog generator)
# macOS:
brew install git-cliff
# or via cargo:
cargo install git-cliff

Configure git-cliff with a cliff.toml and generate a CHANGELOG:

# cliff.toml — git-cliff configuration
[changelog]
header = "# Changelog

All notable changes to this plugin are documented here.
"
body = '''
{% if version %}## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}{% else %}## Unreleased{% endif %}
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group | upper_first }}
{% for commit in commits %}
- {% if commit.breaking %}**BREAKING** {% endif %}{{ commit.message | upper_first }} ([{{ commit.id | truncate(length=7, end="") }}](https://github.com/myorg/my-plugin/commit/{{ commit.id }})){% endfor %}
{% endfor %}
'''
trim = true

[git]
conventional_commits = true
commit_parsers = [
    { message = "^feat", group = "Features" },
    { message = "^fix",  group = "Bug Fixes" },
    { message = "^perf", group = "Performance" },
    { message = "^docs", group = "Documentation" },
    { message = "^chore|^refactor|^style|^test", skip = true },
]
filter_commits = true

# Generate CHANGELOG for all history
git cliff --output CHANGELOG.md

# Generate only the latest release section (since last tag)
git cliff --latest --prepend CHANGELOG.md

# Generate unreleased changes (no tag yet)
git cliff --unreleased

# Bump version and generate in one command
git cliff --bump --output CHANGELOG.md && git add CHANGELOG.md

NOTE: Enforce Conventional Commits format with a commit-msg Git hook — place it at .git/hooks/commit-msg and make it executable: chmod +x .git/hooks/commit-msg. The hook rejects commits that don't match the pattern ^(feat|fix|docs|style|refactor|perf|test|chore)(\(.+\))?(!)?: .+, ensuring your changelog stays clean and automatable.

Leave Comment

Your email address will not be published. Required fields are marked *