Conventional Commits is a lightweight specification for writing commit messages that are human-readable and machine-processable.

Instead of writing vague messages like "fixed bug" or "updates", this convention provides a rigorous rule set for creating an explicit commit history. This makes it easier to understand what happened in a project and why, and it enables potent automation tools (like automatic changelogs and version bumping).


1. Anatomy of a Commit Message

A conventional commit message mimics the structure of an email, with a clear header (subject), optional body, and optional footer.

<type>[optional scope]: <description>

[optional body]

[optional footer(s)]

The Header (Required)

The first line is the most important. It contains three parts:

  1. Type: What kind of change is this? (e.g., feat, fix, chore)
  2. Scope (Optional): What part of the codebase is affected? (e.g., (auth), (checkout))
  3. Description: A short, imperative summary of the change.

The Body (Optional)

This provides more context. Use it to explain the "why" behind the change, not just the "how".

Used for referencing issues or indicating breaking changes.


2. Commit Types (Cheat Sheet)

You only need to memorize a few major types.

Type

Meaning

SemVer Correlation

Example

feat

A new feature for the user.

MINOR (1.1.0)

feat(search): add voice search capability

fix

A bug fix for the user.

PATCH (1.0.1)

fix(login): handle null token gracefully

docs

Documentation only changes.

PATCH

docs: update API usage in README

chore

Maintenance changes that don't affect src or test files.

PATCH

chore: upgrade flutter dependencies

style

Code style changes (formatting, missing semi-colons, etc).

PATCH

style: apply dart format

refactor

A code change that neither fixes a bug nor adds a feature.

PATCH

refactor(auth): simplify login logic

test

Adding missing tests or correcting existing tests.

PATCH

test: add unit tests for user_service

perf

A code change that improves performance.

PATCH

perf: optimize image loading in listview

ci

Changes to CI configuration files and scripts.

PATCH

ci: add github actions workflow


3. Practical Examples

✨ Feature (feat)

Used when adding new functionality.

feat(cart): add "Undo" button after removing item

Allows users to quickly recover an item if they accidentally deleted it.

πŸ› Bug Fix (fix)

Used when fixing a bug.

fix(navigation): prevent double-pushing the home screen

The "Home" button was pushing a new route instead of popping to root.
This caused the navigation stack to grow indefinitely.

Closes #42

πŸ’₯ Breaking Change (!)

There are two ways to mark a breaking change (which triggers a MAJOR version bump):

  1. Using a ! after the type/scope.
  2. Adding a BREAKING CHANGE: footer.

Example 1 (Using !):

feat(api)!: remove support for XML responses

We now strictly return JSON. XML parsers will fail.

Example 2 (Using Footer):

chore: drop support for Node 12

BREAKING CHANGE: The project now requires Node 14 or higher due to new crypto dependencies.

4. Good vs. Bad Examples

See the difference between a messy history and a clean one.

❌ Bad / Vague

βœ… Good / Conventional

Why it's better

fixed it

fix(login): handle timeout error

Tells us what was fixed and where.

added stuff

feat(profile): add user avatar upload

Clearly states the new feature.

wip

(Don't commit WIPs to main)

Keep history clean. Use git rebase to squash WIPs.

changed color

style(theme): update primary button color

Categorizes the change as stylistic.

API change

feat(api)!: rename getAll to fetchAll

The ! warns everyone this is a BREAKING change.


5. Why Should You Care?

  1. Automated Changelogs: You can use tools to generate standard changelogs automatically. No more manual writing!
  2. Semantic Versioning: Tools can look at your commit history (feat, fix, BREAKING) and determine if the next version should be 1.0.1, 1.1.0, or 2.0.0 automatically.
  3. Better Collaboration: When reviewing history (e.g., git log), it's immediately obvious what happened.
    • Scan for fix to see recent bug patches.
    • Scan for feat to see what's new.
  4. Discipline: It forces you to think about the nature of your change. If you can't categorize it, your commit might be doing too many things at once.

6. FAQ

Q: What if I accidentally use the wrong type?

A: If you haven't pushed yet, use git commit --amend. If you have pushed to a shared branch, it’s usually okay to leave it unless your team relies heavily on automated releases.

Q: Can I use my own types?

A: Yes! The spec is flexible. Some teams use build:, revert:, or even emojis. Just be consistent.

Q: Should I use lower case or Title Case?

A: The spec allows either, but lower case is the most common convention in the industry (e.g., feat: not Feat:).

Q: What if a commit does multiple things?

A: That's a sign you should split it! A commit should ideally do one thing. If you fixed a bug AND added a feature, split it into two commits: fix: ... and feat: ....

Q: How do I handle revert commits?

A: The convention suggests using a revert: type. The header should contain the header of the specific commit being reverted, and the body should contain This reverts commit <hash>..

Q: Is there a character limit for the header?

A: It is meant to be a summary. A good rule of thumb is to keep it under 50 characters where possible, and strictly under 72 characters to avoid wrapping in various git tools.

Q: How granular should the scope be?

A: Scopes should be distinct modules or features (e.g., auth, payment, ui). Avoid using precise filenames (like user_service.dart) as scopes; stick to the "concept" of the component.


Reference(s)