All posts
GitConventional CommitsDeveloper ToolsDevOps

Conventional Commits: How to Write Great Git Commit Messages (2026 Guide)

Conventional Commits explained — the format, types (feat/fix/refactor), real examples, breaking changes, and how to automate commits with AI tools.

Nitish YadavMay 16, 2026

If you've ever scrolled through git log --oneline and seen 200 commits that all say "fix stuff", "wip", "asdf", or "trying again", you understand the problem Conventional Commits solves. It's a 10-rule specification for commit messages that turns your git history from a junk drawer into a structured changelog.

This guide explains the Conventional Commits format, the 11 commit types, how to handle breaking changes, real before/after examples from production codebases, and how to write commit messages 10x faster with AI.

What is Conventional Commits?

Conventional Commits is a specification (currently v1.0.0) that defines a lightweight format for git commit messages: <type>(<scope>): <subject>, optionally followed by a body and footer. It was designed to make commit history machine-readable so tools can automatically generate changelogs, determine semantic version bumps, and identify breaking changes. It's the default commit style on most modern open-source projects (Vue, Angular, NestJS, Vercel, Mintlify) and a growing number of company codebases.

Example:

feat(auth): add Google OAuth login

Replaces the email/password flow with Google sign-in for new
users. Existing accounts still work with their old password.

Closes #482

That single commit tells a tool:

  • It's a new feature (feat)
  • In the auth module (scope)
  • That adds Google OAuth login
  • With details about backwards compatibility
  • And references an issue

A script can now bump the version to 1.2.0 (minor — new feature), add this to the changelog under "New features", and tag the release. No human intervention required.

Why use Conventional Commits?

Four concrete reasons.

1. Automated changelogs. Tools like standard-version, semantic-release, and release-please read Conventional Commits and generate complete CHANGELOG.md entries automatically on every release. No more hand-writing release notes from memory.

2. Automated semantic versioning. fix: bumps the patch version (1.0.0 → 1.0.1). feat: bumps the minor (1.0.0 → 1.1.0). feat!: or BREAKING CHANGE: bumps the major (1.0.0 → 2.0.0). Your CI does the math.

3. Better code review. Reviewers can scan the commit list and immediately know what kind of changes are in the PR — refactors are reviewed differently from new features, fixes from style changes.

4. Better bisects. When you're tracking down which commit broke production, git log --oneline | grep "^fix" instantly narrows the search.

The format: type(scope): subject

The full structure:

<type>[optional scope][!]: <description>

[optional body]

[optional footer(s)]

Breakdown:

  • type (required): one of 11 standard types — see below
  • scope (optional): the part of the codebase affected, in parentheses. e.g. auth, api, widget, billing
  • ! (optional): marks a breaking change
  • description (required): a short summary in imperative mood, under 72 characters
  • body (optional): a longer explanation of why the change was made, wrapped at 72 characters
  • footer (optional): metadata like BREAKING CHANGE: ..., Closes #123, Co-authored-by: ...

The 11 commit types

TypeWhen to useVersion bump
featA new feature for the userminor
fixA bug fix for the userpatch
docsDocumentation only changesnone
styleFormatting, missing semicolons, etc. (no logic change)none
refactorCode change that neither adds a feature nor fixes a bugnone
perfPerformance improvementpatch
testAdding or correcting testsnone
buildChanges to the build system or dependenciesnone
ciChanges to CI configuration files and scriptsnone
choreRoutine maintenance (deps, tooling)none
revertReverts a previous commitvaries

Rule of thumb: if it changes user-facing behavior, it's feat or fix. If it changes the codebase but not user-facing behavior, it's one of the others.

Real before/after examples

The format becomes obvious through examples.

❌ Bad commit✅ Conventional Commit
fix stufffix(api): handle 429 from Razorpay webhook with backoff
WIP(don't push WIP commits — squash before merge)
Updated READMEdocs(readme): document new SSO setup steps
Refactorrefactor(auth): extract JWT validation into middleware
Performance improvementsperf(search): switch hybrid retrieval from RRF k=60 to k=80
New featurefeat(widget): add Hindi and Hinglish locale support
breaking change!!!feat(api)!: remove deprecated /v1/chat endpoint
depschore(deps): upgrade Next.js from 15 to 16
add teststest(billing): cover edge case for INR currency override
oopsfix(deploy): correct env var name in docker-compose.prod

The "good" version takes 5-10 seconds longer to write and saves 10x that when someone (including future-you) is reading git log six months later.

How do I handle breaking changes?

Two options that mean the same thing:

Option 1: ! after the type/scope:

feat(api)!: remove deprecated /v1/chat endpoint

The endpoint was deprecated in v1.5.0 with a sunset warning.
Use /v2/chat instead — same request/response shape.

Option 2: BREAKING CHANGE: in the footer:

feat(api): switch chat endpoint to streaming-first

BREAKING CHANGE: /v2/chat now returns Server-Sent Events
instead of a JSON body. Clients must update to handle SSE.
The old buffered response is available at /v2/chat?stream=false
for one minor version, then removed.

Either signals to semantic-release / standard-version to bump the major version (1.x.x → 2.0.0). Use ! for short breaking changes and BREAKING CHANGE: when the explanation is longer.

How do I write commits faster?

Three workflows, escalating in automation.

1. Manual with a cheat sheet

Pin a one-page cheat sheet (the table above) in your team's wiki. Reviewers gently push back when the format is missed. The team gets fluent in a couple of weeks.

2. CLI helper (commitizen, gitmoji-cli)

Install a CLI that prompts you for type/scope/subject and writes a properly-formatted commit:

npm install -g commitizen
git cz   # interactive prompt

Good for individual contributors. Adds 5 seconds per commit but makes the format mechanical.

3. AI-generated from your diff

Paste your git diff or describe your change in plain English, get a perfectly-formatted Conventional Commit back. This is the fastest workflow once you're past the learning phase.

We built a free AI Git Commit Message Generator that does exactly this. It returns 3 commit message options per change (different angles), each in Conventional Commits format under 72 characters, with an optional 72-char-wrapped body for the recommended option. Use it when you want a polished commit message in 5 seconds without thinking about the format rules.

For teams: install a git hook (prepare-commit-msg) that pipes your staged diff to the API and pre-fills the commit message. We've open-sourced an example in our docs at docs.insitechat.ai/api-reference/chat.

How do I enforce Conventional Commits on my team?

Three layers, in order of strictness:

Layer 1: Documentation and convention. Add a CONTRIBUTING.md with examples. PR template asks "Did you write a Conventional Commit?". This works for small teams with good culture.

Layer 2: commitlint pre-commit hook. Install commitlint with the conventional config. It rejects commits that don't match the format. Combine with husky for a pre-commit hook that runs locally:

npm install --save-dev @commitlint/cli @commitlint/config-conventional husky
echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js
npx husky add .husky/commit-msg 'npx --no-install commitlint --edit "$1"'

Layer 3: CI enforcement. Run commitlint in CI on every PR. Block merge if any commit in the PR doesn't conform. This is the only level that actually prevents bad commits from landing on main.

Conventional Commits + monorepos

In a monorepo with multiple packages (e.g., Turborepo, Nx, pnpm workspaces), use the scope to identify the package:

feat(@insitechat/widget): add Hindi locale
fix(@insitechat/api): correct rate-limit window edge case
refactor(@insitechat/shared): extract pricing math into a util

Some teams use a separator (pkg/) or two-part scope (widget.hindi). Pick a convention and stick to it — semantic-release and changesets both parse this correctly when configured.

FAQ

What's the maximum commit subject length?

72 characters. The git documentation recommends 50 chars for the subject, but Conventional Commits practice allows up to 72 because the type prefix consumes 6-10 chars. Lines over 72 wrap awkwardly in git log and email clients.

Can I use Conventional Commits without semantic versioning?

Yes. Many teams adopt Conventional Commits purely for the readability + automated changelog benefits, without using semver. The format works as a documentation standard on its own.

Do I need scope, or can I skip it?

Scope is optional. Skip it for cross-cutting changes (refactor: rename all "user" references to "member"). Use it when the change is clearly scoped (feat(auth): is more informative than feat:). On large codebases, scope is almost always worth including.

What about merge commits?

Merge commits don't need to follow Conventional Commits. Most semantic-release tools ignore them. If you squash-merge PRs, the squash commit should follow the format — usually the PR title becomes the commit subject.

Should the description be present tense or past tense?

Imperative mood, not past tense. "Add retry logic" (imperative) ✓, "Added retry logic" (past tense) ✗. The git documentation gives this rule of thumb: your commit subject should complete the sentence "If applied, this commit will…". "If applied, this commit will add retry logic." That reads naturally; "If applied, this commit will added retry logic" does not.

Can the body be multiple paragraphs?

Yes. Wrap each paragraph at 72 chars. Separate paragraphs with a blank line. Many commits have a one-paragraph body explaining why; longer commits (introducing a new system, deprecating something) often have 2-3 paragraphs.

How do I document a revert?

Use revert: type with the reverted commit hash in the body:

revert: feat(auth): add Google OAuth login

This reverts commit abc1234.

The OAuth flow conflicts with the SSO integration shipping
in v1.3.0. We'll re-introduce it after the SSO work lands.

What if my team disagrees on scope names?

Common point of friction. Solve it by maintaining a .conventional-commits.json or a list in CONTRIBUTING.md with the approved scopes. Linting tools can enforce the allowed list.

TL;DR

  • Format: type(scope): subject — under 72 chars, imperative mood
  • 11 types but you'll use feat, fix, refactor, docs, chore 90% of the time
  • Breaking changes: feat!: or BREAKING CHANGE: in footer
  • Enforcement: commitlint + husky for local, CI check for guarantee
  • Speed: AI tools (our free AI Git Commit Message Generator) write properly-formatted commits in seconds

Adopt Conventional Commits and your git log becomes a readable changelog. Combine it with automated tools and your release notes write themselves.

Ready to try InsiteChat?

Create an AI chatbot trained on your website in minutes.

Get started free