Ctrl + K
Git18 min read

Writing Better Commit Messages

A practical guide to writing clear, consistent, and useful Git commit messages, with examples, Conventional Commits, common mistakes, and team workflows.

Published: 2026-10-05

A Git commit message is a small piece of text, but it can have a significant impact on the maintainability of a project. Good commit messages make it easier to understand why a change was introduced, review a history of changes, investigate bugs, and prepare releases. Poor messages turn Git history into a collection of vague descriptions such as "fix", "changes", or "update".

Writing better commit messages does not require a complicated system. The most important principles are simple: describe the change clearly, explain the reason when it is not obvious, keep the subject concise, and use a consistent format. For teams, conventions such as Conventional Commits can make these rules even easier to apply.

Why Commit Messages Matter

Git records both the code changes and the message attached to each commit. The code shows what changed, while the commit message provides human-readable context. Months later, developers often need that context more than the exact list of modified lines.

A useful commit history can answer questions such as why a component was changed, when a bug was introduced, whether a change was a feature or a fix, and which commits belong to a particular release. This becomes especially valuable in large repositories where hundreds or thousands of commits may be involved.

  • Code reviewers can understand the intention behind a change more quickly.
  • Developers can investigate regressions using Git history.
  • Teams can identify related changes during releases.
  • Release notes and changelogs can be generated more easily.
  • Future maintainers can understand historical decisions.
  • Git history becomes useful documentation instead of a list of vague actions.
💡 A good commit message should make sense to someone who did not write the code and may not remember the change months later.

The Basic Structure of a Commit Message

A commit message can be as simple as a single subject line. For more complicated changes, Git also supports a longer body and optional footer information.

Short subject line

Longer explanation of the change, including important context
and the reason for the implementation.

Optional footer information

The subject should communicate the main change immediately. The body is useful when the change needs additional context, such as explaining a technical decision, describing a non-obvious workaround, or documenting why an alternative approach was not used.

1. Write a Clear Subject

The first line is the most important part of a commit message because it is what developers usually see when scanning Git history. It should describe the main change without unnecessary detail.

Good:
Add pagination to the users table
Fix incorrect total price calculation
Update authentication middleware
Remove unused dashboard styles

Weak:
Changes
Update
Fix
Some fixes
Work

A useful subject normally contains enough information to distinguish the commit from other changes. Instead of saying "fix", identify what was fixed. Instead of "update", describe what was updated.

2. Prefer Specific Descriptions

Specificity is more useful than simply making a commit message longer. A short message can be excellent if it identifies the affected behavior clearly.

Fix login redirect after session expiration

Add loading state to product search

Prevent duplicate form submissions

Handle empty API responses in dashboard

These messages describe observable changes rather than vague activity. A developer looking at the history can understand the purpose without opening the entire diff.

3. Describe One Logical Change

A commit is easier to understand when it represents one logical change. This does not mean that every commit must modify only one file. A single feature may legitimately require changes to components, styles, API calls, tests, and configuration.

The important distinction is between related changes and unrelated changes. Combining a bug fix, dependency upgrade, formatting changes, and an unrelated refactor in one commit makes the history harder to review and revert.

Good:
Add password reset form
Fix password validation
Add password reset API integration

Less useful:
Add password reset, update dependencies, reformat components, and
refactor unrelated authentication code
💡 Before committing, ask whether you could describe the entire commit with one clear sentence. If not, the changes may need to be separated.

4. Explain Why When It Matters

The code often makes it possible to determine what changed. It does not always explain why the change was necessary. Commit bodies are particularly valuable when the reason behind a decision is not obvious from the code.

Increase API timeout for report generation

Report generation can take longer than normal because the endpoint
processes large datasets. The previous timeout caused valid requests
to fail before the server completed the operation.

This explanation provides context that may not be obvious from a one-line diff. Future developers can understand the reason for the timeout instead of assuming that the value was changed arbitrarily.

5. Keep the Subject Concise

A commit subject should be short enough to scan easily in tools such as git log, GitHub, GitLab, or IDE interfaces. There is no universal maximum that applies to every project, but keeping the subject around 50 to 72 characters is a common convention.

The exact limit matters less than consistency. If a change genuinely needs more explanation, put the additional information in the commit body instead of creating an extremely long subject.

6. Use an Imperative Style

Many projects write commit subjects in the imperative mood. The idea is that the subject completes an implied instruction such as "If applied, this commit will...".

Add user search
Fix broken navigation
Remove deprecated API call
Update validation rules

This style is common because it creates consistent commit histories. However, the most important requirement is to follow the convention used by the project rather than mixing several writing styles.

7. Avoid Unnecessary Details

A commit message should provide useful context, not reproduce the entire implementation. Listing every changed function, variable, and CSS selector usually makes the message harder to read without adding much value.

Good:
Fix mobile navigation overflow

Too detailed:
Change .nav-container width from 100% to 98%, update
NavMenu.tsx line 42, add overflow-x hidden, modify padding,
change the button margin, and update the breakpoint

The diff already contains implementation details. The commit message should focus on the meaningful change and, when necessary, its reasoning.

8. Use the Commit Body for Context

The body is useful when a subject alone cannot communicate enough information. It can describe a problem, explain a decision, document compatibility considerations, or mention important limitations.

Fix duplicate requests during search

Rapid input changes could trigger multiple requests before the previous
request completed. The search now waits for the debounce interval before
sending the latest query.

A body is especially useful for changes involving workarounds or decisions that future developers might otherwise question.

9. Avoid Temporary Commit Messages

Messages such as "test", "wip", "try again", and "fix" may be acceptable on a private branch during active development. They become problematic when they remain in the permanent history of a shared branch.

Before merging, developers often squash or reword temporary commits so that the final history describes meaningful changes. The exact workflow depends on the team's Git policy.

Temporary local work:
WIP
Try another approach
Fix test

Useful final history:
Add search filters
Handle empty search results
Fix filter state restoration

Conventional Commits

Conventional Commits is a widely used convention for structuring commit messages. It adds a type and optional scope before the subject, making commits easier to categorize and process automatically.

feat: add user profile editing
fix: prevent duplicate form submissions
docs: update API authentication guide
refactor: simplify permission checks
test: add coverage for password reset
chore: update development dependencies

The general structure is a type followed by an optional scope, a colon, and a short description.

<type>(<scope>): <description>

For example, a frontend project might use a scope to identify the affected area of the application.

feat(auth): add password reset
fix(search): handle empty results
refactor(api): simplify request handling

Common Conventional Commit Types

The Conventional Commits specification defines a convention rather than requiring every project to use exactly the same complete list of types. In practice, several types are especially common.

TypeTypical purposeExample
featAdd or extend functionalityfeat: add export to CSV
fixCorrect a bugfix: handle expired sessions
docsDocumentation changesdocs: update installation guide
refactorChange code structure without changing intended behaviorrefactor: simplify auth service
testAdd or modify teststest: cover invalid email input
choreMaintenance changeschore: update dependencies
perfPerformance improvementsperf: reduce dashboard queries
styleFormatting or style-only changesstyle: format API module

Teams can document which types they use and how they should be applied. Consistency is more important than creating a large number of categories that developers interpret differently.

Breaking Changes in Commit Messages

When using Conventional Commits, breaking changes can be explicitly marked. One common approach is adding an exclamation mark after the type or scope.

feat!: remove legacy authentication endpoint
feat(api)!: change response format for user profiles

A breaking change can also be described in a footer when the project follows the relevant Conventional Commits rules.

feat(api): change user response format

BREAKING CHANGE: the username field is now returned as displayName.

The exact convention should match the project's tooling and documentation. This information can be particularly useful when automated release and changelog systems classify commits.

Commit Messages and Code Review

Commit messages can make code review easier when commits are logically organized. A reviewer can inspect a sequence of focused commits and understand how the implementation evolved.

For example, a feature might contain one commit for the initial implementation, another for validation, and another for tests. Whether such commits should remain separate or be squashed depends on the team's review and merge workflow.

A good commit message should not replace a good pull request description. The commit communicates the purpose of an individual change, while the pull request can describe the broader feature, testing strategy, screenshots, limitations, and related tasks.

Commit Messages and Git History

Git history is often used as a debugging and maintenance tool. Commands such as git log, git show, git blame, and git bisect become more useful when commits have clear descriptions.

git log --oneline
git show <commit>
git blame <file>
git bisect start

For example, if a developer notices that a behavior changed after a particular release, descriptive commit subjects can make it easier to identify potentially relevant changes before inspecting individual diffs.

Commit Messages and Changelogs

Consistent commit messages can also support release automation. If commits use predictable categories such as feat and fix, tools can classify changes and help generate release notes or changelogs.

This does not mean that every commit message should be written specifically for an automated changelog. The message should first be useful to developers. Automation is a secondary benefit of having a consistent structure.

Common Commit Message Mistakes

Several patterns repeatedly make Git history less useful. Most are easy to avoid once the purpose of a commit message is understood.

ProblemExampleBetter approach
Too vagueFixFix incorrect cart total
No contextUpdate codeHandle empty API response
Too broadUpdate everythingSplit unrelated changes into focused commits
Implementation dumpChange five files and update stylesDescribe the user-visible or logical change
Temporary messageWIPRewrite before merging into shared history
Inconsistent styleAdded feature / fix bug / Updating APIFollow one project convention
Unnecessary ticket detailsPROJ-1234-ABC-5678-final-v2Use a readable subject and add issue references separately when appropriate

Should Every Commit Have a Body?

No. A body is not necessary for every commit. A small, obvious change may be completely explained by its subject.

fix: correct button alignment

A body becomes more valuable when the change involves a non-obvious decision, compatibility issue, workaround, migration, performance trade-off, or other context that cannot be understood from the subject.

Commit Message Length: How Long Is Enough?

There is no universal requirement for commit message length. The subject should generally be concise, while the body should contain enough information to explain important context without repeating the diff.

A useful rule is to write the shortest message that communicates the change accurately. Add more information when someone maintaining the project later would otherwise have to reconstruct the reasoning from code, issue trackers, or external discussions.

Using Issue or Ticket References

Many development teams connect Git commits with issues, tickets, or tasks. A project may require an identifier in the commit subject or footer, while another project may keep issue references in pull requests instead.

fix: prevent duplicate checkout requests

Refs: PROJ-184

Follow the project's established convention. Avoid filling the subject with internal identifiers if they make the message difficult to understand.

Writing Commit Messages for Frontend Projects

Frontend repositories often contain many small changes to components, styles, API integrations, routing, state management, and accessibility. Clear commit messages can make these changes easier to distinguish.

feat: add user profile page
fix: preserve filter state after navigation
fix: prevent modal from closing on submit
refactor: simplify product list state
perf: defer non-critical dashboard widgets
a11y: improve keyboard navigation for dropdown

If a project uses Conventional Commits, custom types such as a11y may be allowed by the team's convention. Otherwise, the change can be categorized according to the types already supported by the project.

Writing Commit Messages for Backend Projects

Backend changes often benefit from identifying the affected API, service, database operation, or background process.

feat(api): add order cancellation endpoint
fix(auth): reject expired refresh tokens
refactor(users): simplify repository queries
perf(search): add database index for product lookup

The scope is optional, but it can be useful in larger repositories where several parts of the system are changed frequently.

Formatting Multi-line Commit Messages

When a commit needs a body, keep the subject and body visually separated. This makes the message easier to scan in Git clients and command-line tools.

Improve report export performance

The export operation previously loaded all records into memory.
The implementation now processes records in smaller batches to
reduce peak memory usage.

A body can contain multiple paragraphs when the change requires it. Keep the explanation focused on information that will remain useful after the immediate development task is finished.

A Practical Commit Message Workflow

You do not need to spend several minutes writing every commit message. A simple workflow is enough for most changes.

  • Review the changes that will be included in the commit.
  • Identify the main logical change.
  • Write a short subject describing that change.
  • Check whether the reason or important context needs explanation.
  • Add a body if the subject does not provide enough context.
  • Apply the project's formatting or Conventional Commit convention.
  • Make sure the message does not describe unrelated changes.
git status
git diff --staged
git commit -m "fix: prevent duplicate checkout requests"
💡 Use git diff --staged before committing. It helps confirm that the commit message actually describes the changes being committed.

When to Split a Commit

If a staged change contains several unrelated logical changes, splitting the commit can make the history easier to understand. For example, a feature implementation and an unrelated formatting cleanup generally have different purposes and can be committed separately.

Splitting commits is especially useful when individual changes may need to be reverted, reviewed, cherry-picked, or investigated independently.

git add src/features/search
git commit -m "feat: add product search"

git add src/components/header
git commit -m "fix: correct header navigation"

When to Squash Commits

Squashing combines multiple commits into a smaller number of commits. It is often used before merging a feature branch when the intermediate commits contain temporary work, corrections, or implementation details that do not provide useful long-term history.

Squashing is a workflow choice rather than a requirement for good commit messages. Some teams prefer a detailed sequence of commits, while others prefer a clean history with one or a few logical commits per feature.

Commit Messages in Team Projects

The best commit message convention is one that the entire team understands and applies consistently. A short contribution guide can document the expected subject style, allowed Conventional Commit types, scope rules, body requirements, and issue references.

Automated commit validation can also prevent accidental violations. For example, a project can check commit subjects in CI or use Git hooks to enforce a format.

However, automation should enforce a useful convention rather than encourage developers to write technically valid but meaningless messages. A commit such as "fix: changes" may follow the syntax while still providing almost no useful information.

A Simple Commit Message Checklist

  • Does the subject clearly describe the main change?
  • Is the message specific rather than vague?
  • Does the commit represent one logical change?
  • Is the subject concise?
  • Does it follow the project's writing style?
  • If using Conventional Commits, is the type correct?
  • Would a future developer understand the purpose?
  • Does the commit body explain important non-obvious context?
  • Are temporary messages such as WIP removed before merging?
  • Does the message avoid unnecessary implementation details?

Good Commit Message Examples

feat: add dark mode preference
fix: prevent expired sessions from redirecting incorrectly
refactor: extract reusable pagination component
docs: explain local development setup
test: cover invalid password reset tokens
perf: reduce duplicate product API requests

fix(search): handle empty search results
feat(auth): add password reset flow
refactor(api): simplify error handling

These examples work because they identify a meaningful change without attempting to describe every implementation detail. If additional context is important, it can be placed in the body.

Helpful Git Tools

Several categories of developer tools can make commit-related workflows faster. A Git commit generator can help create an initial message from a description of the change. A Conventional Commit generator can format messages according to a chosen convention.

Release notes and changelog generators can also benefit from consistent commit categories. A Git branch name generator can help maintain a predictable naming convention for the branches where commits are developed.

What makes a good Git commit message?

A good Git commit message clearly describes the main logical change, uses concise and specific wording, follows the project's convention, and adds a body when important context or reasoning is not obvious from the subject.

How long should a Git commit message be?

The subject should generally be concise, often around 50 to 72 characters by convention. The body can be longer when additional context is useful. There is no universal maximum that applies to every project.

Should Git commit messages use the imperative mood?

Many projects prefer the imperative mood, such as "Add search filters" or "Fix authentication error". The main goal is consistency, so follow the convention established by the project.

What is a Conventional Commit?

A Conventional Commit follows a structured format such as "feat: add search" or "fix: handle expired sessions". The convention makes commits easier to categorize and can support automated release and changelog workflows.

Should every commit have a body?

No. A short subject is sufficient for simple and obvious changes. A body is useful when the change needs additional reasoning, context, migration details, compatibility information, or an explanation of a non-obvious decision.

Is it okay to use WIP as a commit message?

WIP can be useful for temporary commits on a private branch, but such messages are usually not ideal for permanent shared history. Before merging, temporary commits can be rewritten, squashed, or replaced with meaningful messages according to the team's workflow.

Can commit messages be used to generate changelogs?

Yes. Consistent commit conventions, especially structured formats such as Conventional Commits, can make it easier for release tooling to classify changes and generate changelogs or release notes. The exact automation depends on the project's tooling.

Conclusion

Writing better Git commit messages is mainly about providing useful context in a consistent format. A strong subject identifies the logical change, while an optional body explains important reasoning that cannot be understood from the diff alone.

Avoid vague messages, unrelated changes, unnecessary implementation details, and temporary descriptions in permanent history. If your project uses Conventional Commits, follow its structure consistently and document the convention for the team. Over time, these simple practices turn Git history into a much more useful source of information for development, debugging, code review, and releases.

Found an issue?

Found an error, outdated information, or something missing from this article? Let me know through the Contact page.

Your feedback helps improve our articles and keep them accurate and useful.