Skip to content

Conventional Commits: A History Humans Can Read

Tired of WIP? You want to automate changelog generation with actual separation of concerns? Your OCD is triggered looking at your team's lack of homogeneity in commits messages? Conventional Commits are going to be your best friends. Made popular in the Angular project, it's a simple set of rules for your commit messages that makes your log readable, and unlocks powerful automation.

By formatting our commit messages in a standard way, we can automatically generate changelogs, calculate the next version number, and immediately understand the impact of every change.

The Structure

The structure of a conventional commit is simple but strict:

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

[optional body]

[optional footer(s)]

Let's break that down.

1. Type

This tells us the kind of change you made. The two most important types directly affect the project's version number:

  • feat: A new feature for the user. (Corresponds to a MINOR version bump in SemVer)
  • fix: A bug fix for the user. (Corresponds to a PATCH version bump in SemVer)

We also use other types for internal changes that don't affect the version number. Here's a list of notable ones:

TypeDescription
featA new feature
fixA bug fix
docsDocumentation only changes
styleCode style changes (formatting, semi-colons, etc.)
refactorA code change that neither fixes a bug nor adds a feature
perfA code change that improves performance
testAdding missing tests or correcting existing ones
buildChanges that affect the build system or dependencies
ciChanges to our CI configuration files and scripts
choreOther changes that don't modify source or test files
revertReverts a previous commit

What Users See

While all these types are useful for developers, feat and fix are typically the only commits appearing in a user-facing changelog. The rest are for internal tracking. BREAKING CHANGE footers, regardless of type, will always be highlighted.

2. Scope (Our Special Rule)

The scope provides context for the change. It tells us what part of the codebase is affected.

Don't skip the scope

For our workflow, the scope is not optional. At the minimum, specify the layer of your app you are working on, but if you are using a ticketing system (e.g., Jira, Azure DevOps, etc.), your could also use it for the ID of the task you are working on. This is easily parsable and links every change back to a specific requirement.

Correct: feat(JIRA-123): ...

Incorrect: feat: ...

3. Description

A short, imperative summary of the change in 50 characters or less. This is the first line of the commit that shows up on shortened git logs.

Ideally, it should use an "imperative style" to clearly states the change made.

  • Do: Add new login button
  • Don't: Added a new login button or Adds new login button

4. Body (Optional)

If your change is complex, you can provide a longer description in the body. Explain the "what" and "why", not the "how."

The footer is used to reference issues or, most importantly, to signal a breaking change.

To indicate a breaking change, start a new line in the footer with BREAKING CHANGE: followed by a description of what broke. This will trigger a MAJOR version bump.

shell
feat(GG-21): Add doors

Doors present in every room.
Add a front door to the main entrance.

BREAKING CHANGE: Added lock to the front door.

You can also output some metadata for your changelog. Follow the git trailers format for maximum compatibility:

shell
Authored-by: Maelle <alicia@example.com>

Putting It All Together: Examples

Example 1: A New Feature

shell
feat(GG-123): Add user logout button to settings page

Users can now log out from their account by clicking the new button on the settings page.

Example 2: A Bug Fix

shell
fix(GG-456): Prevent form submission with invalid email

The registration form no longer accepts email addresses without an '@' symbol, preventing bad data from being sent to the API.

Example 3: A Refactor with a Breaking Change and git trailer footers

shell
refactor(GG-789): Rework authentication middleware

The authentication logic has been moved from the controller layer to dedicated middleware. This simplifies the API controllers and centralizes the auth flow.

BREAKING CHANGE: The `/auth/token` endpoint now returns an object `{ "accessToken": "..." }` instead of a raw token string. All clients must be updated to handle the new response structure.
Authored-by: Maelle <alicia@example.com> 
Signed-off-by: Sciel <sciel@example.com> 
Signed-off-by: Lune <lune@example.com>

The Practical Payoff

This isn't just about keeping a tidy house. This discipline is what makes automation possible, and it will save you from future headaches.

  • Automated Changelogs: Stop wasting time writing release notes by hand. A conventional log allows a script to generate an accurate, meaningful changelog for every release, separating new features from bug fixes automatically.

  • Predictable Versioning: Tools like GitVersion can parse this history and determine the next correct SemVer version. A feat(...) would increment the minor digit, while a fix(...) increments the patch digit... And any BREAKING CHANGE would, of course, increment the major digit.

  • A Maintainable History: Six months from now, when you're hunting for a regression, a git log that you can actually read is invaluable. You'll know what changed, why it changed, and how big the blast radius was, without having to decipher a dozen "WIP" messages.

For the complete specification, see the official Conventional Commits website.