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.
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.
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 informationThe 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
WorkA 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 dashboardThese 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 code4. 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 rulesThis 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 breakpointThe 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 restorationConventional 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 dependenciesThe 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 handlingCommon 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.
| Type | Typical purpose | Example |
|---|---|---|
| feat | Add or extend functionality | feat: add export to CSV |
| fix | Correct a bug | fix: handle expired sessions |
| docs | Documentation changes | docs: update installation guide |
| refactor | Change code structure without changing intended behavior | refactor: simplify auth service |
| test | Add or modify tests | test: cover invalid email input |
| chore | Maintenance changes | chore: update dependencies |
| perf | Performance improvements | perf: reduce dashboard queries |
| style | Formatting or style-only changes | style: 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 profilesA 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 startFor 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.
| Problem | Example | Better approach |
|---|---|---|
| Too vague | Fix | Fix incorrect cart total |
| No context | Update code | Handle empty API response |
| Too broad | Update everything | Split unrelated changes into focused commits |
| Implementation dump | Change five files and update styles | Describe the user-visible or logical change |
| Temporary message | WIP | Rewrite before merging into shared history |
| Inconsistent style | Added feature / fix bug / Updating API | Follow one project convention |
| Unnecessary ticket details | PROJ-1234-ABC-5678-final-v2 | Use 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 alignmentA 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-184Follow 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 dropdownIf 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 lookupThe 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"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 handlingThese 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.