Conventional Commits

What "conventional commits" are and how I use them

Conventional commits are a set of conventions or rules that are applied to commit messages to ensure better readability by humans and easier automation.

The syntax is as follows:

<type>[optional scope]: <description>

[optional body]

[optional footer(s)]


Every commit has a type (the kind of change being made), a scope (identifier of the section of code being updated - e.g. feat(api):), a description (describing the change).

The commit body is free-form and can describe the commit in as much detail as necessary.

The commit can also have footers which follow the format of key: value pairs. These can be used for any purpose. The only footer mentioned in the spec is the BREAKING_CHANGE: footer which is used to describe a breaking change.

If a commit introduces a breaking change, the commit should flag that in two ways:

  1. Add a ! at the end of the <type> in the first line of the commit message
  2. (optional) Add a BREAKING_CHANGE footer that describes the breaking change

Note that breaking changes can be introduced with any type of commit

🤔 Why?

So why would you do this?

For humans it makes things a bit easier because a standard format makes things easier to read.

For automation it makes things a lot easier by allowing tools to understand what kind of changes are being made and how those changes affect the software being built. For example, a feat: commit can be read as a new feature, so the version should increase at least the minor version, or a commit with a BREAKING_CHANGE footer will indicate that the major version should be incremented.

This syntax also makes it easy to auto-generate a project CHANGELOG and handle splitting different change types into groups or ignoring certain change types from the changelog file.

🔎 Details

The format does not restrict which types are available, it is up to the team or project to decide which ones they want to use. That being said, there are some "standard" types that most projects use.

When I'm writing a commit for one of my projects I go through the following types in the following order to find the right one. That is because it can be confusing which one applies to a commit, since multiple types could be applied to the same commit, depending on how you look at it.

  • feat
    • Does this add a new user-facing feature that didn't exist before?
  • fix
    • Does this fix something that wasn't working correctly?
  • docs
    • Does this add or update documentation for the project? (including code documentation)
  • refactor
    • Does this update non-test code without any changes in functionality?
    • Includes style changes
  • test
    • Does this change only update test code?
    • Includes updating existing tests, refactoring tests, or adding new ones
  • chore
    • Is this a routine task or maintenance that doesn't affect any functionality?
    • For example: updating dependencies
  • build
    • Does this affect the build process or build assets for the project?
  • ci
    • Does this affect the CI/CD pipeline used for the project?

Another important question to ask when creating a commit is: Is this a breaking change?

How you define a breaking change will depend on the project, but examples include: removing a CLI option, removing an API, changing the behaviour of an API. Another way to think of it is: If a user updates to this version without anything else, will something break or behave in a different way?

If it is a breaking change, then you should add the ! at the end of the type in the commit message and (optionally) add a BREAKING_CHANGE: footer to the commit message.

An example could look like:

refactor!: remove api endpoint to fetch todo list


BREAKING_CHANGE: Removed the `/api/v1/todos` endpoint, it has been replaced with `/api/v2/todos`.