What Are Git Commit Message Conventions?
Git commit message conventions are structured formatting rules that dictate how developers should write commit messages. Instead of vague, one-line notes like fixed bug or updated code, a convention provides a consistent template that makes every commit easy to read, parse, and understand at a glance. The most widely adopted convention is the Conventional Commits specification, which builds upon the Angular commit message format pioneered by the AngularJS team.
A conventional commit message follows this structure:
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
The type is a mandatory prefix that categorizes the commit's purpose. Common types include feat (new feature), fix (bug fix), docs (documentation), style (formatting), refactor (code restructuring), test (adding or updating tests), chore (maintenance tasks), and perf (performance improvements). The optional scope provides additional contextual information, such as the component or module affected. The description is a concise summary in the imperative mood. The optional body and footer allow for detailed explanations, breaking change notices, and issue references.
Why Commit Message Conventions Matter
🚀 Deploy your AI agent in 10 minutes
Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.
Try it free →Adopting a commit message convention brings immediate and long-term benefits to any software project. Here is why it matters:
- Machine-readable changelogs: Tools like
standard-versionandsemantic-releasecan automatically parse conventional commit messages to generate changelogs and determine the next semantic version bump. Afeatcommit triggers a minor version increase, afixtriggers a patch, and aBREAKING CHANGEfooter triggers a major version bump. - Improved collaboration: When every team member follows the same format, reviewing git history becomes predictable. You can quickly scan commit logs and understand exactly what changed without opening the diff.
- Better code archaeology: Months or years later, structured commit messages help developers understand why a change was made. The body and footer sections preserve context that would otherwise be lost.
- CI/CD integration: Build pipelines can reject commits that don't follow the convention, enforce quality gates, or skip builds for non-functional changes like
docsorstylecommits. - Onboarding efficiency: New contributors learn the project's standards quickly because the convention is explicit and tool-enforceable.
How to Use Conventional Commits
Implementing a commit message convention requires three steps: learning the format, writing commits correctly, and optionally setting up automated enforcement tools. Below is a complete walkthrough.
1. The Basic Format
The simplest conventional commit looks like this:
feat: add user authentication endpoint
This tells everyone that the commit introduces a new feature, specifically an authentication endpoint. No scope is needed when the description is self-explanatory. For larger codebases, adding a scope narrows the context:
feat(api): add rate limiting middleware
fix(auth): resolve token expiration edge case
Notice the description is always in the imperative mood — "add" not "added," "resolve" not "resolved." This aligns with git's own built-in commands like git merge or git rebase.
2. Writing a Multi-Line Commit Message
For complex changes, use the body and footer. The first line (subject) must be 50 characters or fewer, followed by a blank line, then the body wrapped at 72 characters, and finally any footers:
fix(auth): resolve token refresh race condition
When multiple refresh requests arrived concurrently, the old token
was incorrectly invalidated before the new one was persisted. This
caused authenticated clients to receive 401 responses until the
next manual login.
The fix serializes refresh operations using a per-user mutex and
adds a grace period of 5 seconds during which the old token
remains valid.
Closes: #423
BREAKING CHANGE: The refresh endpoint now returns 409 Conflict
instead of silently dropping duplicate requests. Clients must
implement retry logic.
Key points: a blank line separates subject from body. The body explains what and why — not just the mechanics. The footer contains structured metadata. BREAKING CHANGE must be uppercase and followed by a colon and space. Issue references like Closes: #423 or Refs: #423 are common footer entries.
3. Common Types Reference
Here are the most frequently used types with practical examples:
feat: introduce user profile picture upload
feat(ui): implement dark mode toggle
fix: correct date formatting in invoice PDF
fix(parser): handle malformed JSON gracefully
docs: update API authentication section
docs(readme): add Docker deployment instructions
style: normalize indentation across modules
style(css): remove unused utility classes
refactor: extract logging into shared service
refactor(db): replace raw queries with ORM methods
test: add unit tests for password hashing
test(e2e): cover checkout flow with happy-path scenarios
chore: bump dependency versions for security audit
chore(ci): migrate from Travis CI to GitHub Actions
perf: optimize image compression pipeline
perf(api): implement response caching layer
4. Automated Enforcement with Commitlint
Manually remembering the convention is error-prone. The commitlint tool validates commit messages automatically. Install it along with husky to run checks before every commit:
# Install dependencies
npm install --save-dev @commitlint/cli @commitlint/config-conventional husky
# Create commitlint configuration file
echo "module.exports = { extends: ['@commitlint/config-conventional'] };" > commitlint.config.js
# Initialize husky and add a commit-msg hook
npx husky init
echo "npx --no -- commitlint --edit \$1" > .husky/commit-msg
chmod +x .husky/commit-msg
Now, any commit message that doesn't follow the convention will be rejected:
$ git commit -m "added new feature"
⧗ input: added new feature
✖ subject must be lower-case [subject-case]
✖ type must be one of [type-enum]
✖ found 2 problems, 0 warnings
The error tells you exactly what to fix. Adjust your message to feat: add new feature and the commit proceeds normally.
5. Interactive Commit Creation with Commitizen
If you prefer a guided experience, commitizen provides an interactive prompt that builds the message step by step:
# Install commitizen globally or locally
npm install --save-dev commitizen cz-conventional-changelog
# Add a script to package.json
npx commitizen init cz-conventional-changelog --save-dev --save-exact
# Now use 'git cz' instead of 'git commit'
git cz
Running git cz launches a terminal wizard that asks for the type, scope, description, body, and footer — ensuring the final message is always valid.
6. Integrating with Semantic Release
The ultimate payoff of conventional commits is fully automated versioning and publishing. The semantic-release package reads your commit history, determines the next version, updates the changelog, and publishes the release:
# Install semantic-release and plugins
npm install --save-dev semantic-release @semantic-release/commit-analyzer \
@semantic-release/release-notes-generator @semantic-release/github
# Create a release configuration file (.releaserc.json)
{
"branches": ["main"],
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/github"
]
}
After setting up, simply push to your main branch. The CI pipeline will analyze commits, bump the version, and create a GitHub release with a generated changelog — all based on your commit messages.
Best Practices for Commit Messages
Beyond the mechanical format, these practices elevate the quality of your commit history:
Keep the Subject Line Short and Imperative
The subject line should be a 50-character-or-less summary that completes the sentence: "This commit will...". Write feat: add search filter not feat: added search filter. A short subject prevents truncation in git log outputs, GitHub interfaces, and email notifications.
Separate Subject from Body with a Blank Line
Tools parse the blank line to distinguish the summary from the details. Without it, the entire message becomes the subject. Always include that blank line when you have a body.
# Correct
fix(cart): handle empty cart checkout attempt
Previously the checkout endpoint returned a 500 error when the cart
was empty. Now it returns a 400 with a descriptive error message.
# Incorrect — body merges into subject
fix(cart): handle empty cart checkout attempt
Previously the checkout endpoint returned a 500 error...
Use the Body to Explain "What" and "Why", Not "How"
The diff already shows how the code changed. The commit message should explain what the change accomplishes and why it was necessary. For example:
perf(db): add composite index on orders table
Query profiling revealed that filtering orders by both customer_id
and created_at performed a full table scan under load. The composite
index reduces query time from ~800ms to ~15ms on our production
dataset of 2.3 million rows.
Reference Issues and Pull Requests
Use the footer to link commits to your project's tracking system. Common keywords like Closes, Fixes, Resolves, or Refs are recognized by GitHub, GitLab, and Bitbucket to automatically close or associate issues:
fix(oauth): handle expired refresh tokens gracefully
Fixes: #512
Refs: #489, #490
Mark Breaking Changes Explicitly
Any commit that alters the public API or introduces incompatible behavior must declare BREAKING CHANGE in the footer. This is critical for semantic versioning consumers:
feat(api): migrate authentication to JWT-based tokens
BREAKING CHANGE: The /login endpoint now returns a JWT in the
Authorization header instead of a session cookie. Clients must
update to store and resend the token on every request.
Avoid Committing Unrelated Changes Together
Each commit should be atomic — a single logical change. If you fix a bug and also reformat a file, split them into two commits: fix: ... and style: .... This keeps the history clean and makes reverting specific changes trivial.
Use Scopes Consistently
Define a short list of scopes that map to your project's components (e.g., api, ui, db, auth, docs). Document them in your contributing guidelines. Consistent scopes make filtered log views like git log --oneline --grep="scope: api" highly effective.
Conclusion
Git commit message conventions transform your version control history from a cryptic log into a structured, machine-readable narrative. By adopting the Conventional Commits format, you unlock automated changelogs, deterministic semantic versioning, and streamlined CI/CD pipelines. The investment is minimal — learn the types, write in the imperative mood, and optionally enforce compliance with commitlint or commitizen. Teams that embrace this practice report faster code reviews, easier onboarding, and fewer "what does this commit do?" conversations. Start with your next commit: pick the right type, craft a concise description, and let your git history tell a clear story.