← Back to DevBytes

Git Commit Message Conventions

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:

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.

🚀 Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started — $23.99/mo
← Back to all articles