Skip to main content

Conventional Commits — Structured Commit Messages

What is the Conventional Commits Specification?

Conventional Commits is a lightweight convention for writing commit messages in a machine-readable, human-understandable format. It enables:

  • Automated changelog generation (CHANGELOG.md)
  • Automated semantic version bumping (patch / minor / major)
  • Searchable, filterable history by type and scope
  • Consistent onboarding — every developer writes messages the same way

Format

<type>(<scope>): <subject>

[optional body]

[optional footer(s)]

Rules

  • type — required, lowercase
  • scope — optional, in parentheses, describes the component affected
  • subject — required, imperative mood, no trailing period, max 72 chars
  • Blank line between subject and body
  • Body explains what and why, not how
  • Footer contains BREAKING CHANGE: or issue references

Types

TypeWhen to UseVersion Bump
featA new featureMINOR (1.x.0)
fixA bug fixPATCH (1.0.x)
refactorCode change that is neither a fix nor a featureNone
testAdding or updating testsNone
docsDocumentation changes onlyNone
choreBuild process, dependencies, toolingNone
perfPerformance improvementPATCH
ciCI/CD configurationNone
styleFormatting, whitespace (no logic change)None
revertReverting a previous commitDepends

A BREAKING CHANGE: footer or ! after the type triggers a MAJOR bump (x.0.0).


Examples

Minimal (subject only)

feat(transactions): add CSV export endpoint
fix(auth): resolve token refresh race condition
chore(deps): upgrade Spring Boot from 3.1.5 to 3.2.1

With body

feat(transactions): add date range filter to listing API

Previously the listing API returned all transactions for a user
with no ability to filter by date. This becomes unworkable for
users with > 6 months of history.

The filter accepts ISO-8601 dates (fromDate, toDate) and defaults
to returning all records when no filter is supplied.
feat(api)!: rename transaction amount field to amountMinorUnits

BREAKING CHANGE: The `amount` field in TransactionDto has been renamed
to `amountMinorUnits` and now returns an integer (minor currency units)
instead of a decimal. Clients must update their deserialization logic.

Closes JIRA-201

With multiple footers

fix(transactions): prevent duplicate export jobs on concurrent requests

Added an idempotency key to the export job creation. Concurrent requests
with the same userId and date range now return the existing job rather
than creating a duplicate.

Reviewed-by: Jane Smith <jane@example.com>
Closes JIRA-345
Co-authored-by: Bob Johnson <bob@example.com>

Revert

revert: feat(transactions): add CSV export endpoint

This reverts commit a3f9bc2.
Reason: export feature caused memory pressure under load — tracked in JIRA-399

Scopes for Java/Spring Projects

Define scopes that map to your service's bounded contexts or layers:

feat(auth): ...           # authentication / authorisation
feat(transactions): ... # transaction domain
feat(export): ... # export feature
feat(payments): ... # payment processing
feat(api): ... # API layer / contracts
feat(db): ... # database / migrations
feat(config): ... # configuration
feat(kafka): ... # messaging / events
feat(security): ... # security configuration
chore(deps): ... # dependency management
chore(build): ... # Maven/Gradle configuration
ci(github): ... # GitHub Actions
docs(api): ... # API documentation

Automating Changelogs

Using conventional-changelog-cli

npm install -g conventional-changelog-cli

# Generate changelog from the last release tag to now
conventional-changelog -p angular -i CHANGELOG.md -s -r 1

Generated CHANGELOG.md

## [1.2.0] - 2024-03-15

### Features
- **transactions:** add CSV export endpoint (JIRA-113) (a3f9bc2)
- **transactions:** add date range filter to listing API (JIRA-105) (9f3e2d1)
- **auth:** support OAuth2 PKCE flow (JIRA-98) (7d1e4f0)

### Bug Fixes
- **transactions:** resolve NPE on null description (JIRA-199) (b2c3d4e)
- **auth:** fix token refresh race condition (JIRA-201) (1a4b5c6)

### BREAKING CHANGES
- **api:** rename transaction amount field to amountMinorUnits (JIRA-201)

Enforcing Conventions with a commit-msg Hook

#!/bin/bash
# .git/hooks/commit-msg (or scripts/hooks/commit-msg)

COMMIT_MSG=$(cat "$1")

# Allow merge and revert commits
echo "$COMMIT_MSG" | grep -qE "^(Merge|Revert)" && exit 0

# Conventional Commits pattern
PATTERN="^(feat|fix|refactor|test|docs|chore|perf|ci|style|revert)(\(.+\))?!?: .{1,100}"

if ! echo "$COMMIT_MSG" | grep -qE "$PATTERN"; then
echo ""
echo "❌ Commit message does not follow Conventional Commits."
echo ""
echo " Format: <type>(<scope>): <subject>"
echo " Example: feat(transactions): add date range filter"
echo " Types: feat | fix | refactor | test | docs | chore | perf | ci | style | revert"
exit 1
fi

See Hooks for setup instructions.


Keep Scopes Consistent

The value of scopes comes from consistency — if one developer writes feat(transaction): and another writes feat(transactions):, changelog grouping breaks. Define your project's allowed scopes in a commitlint.config.js or team wiki and enforce them in the hook.