Six months from now, someone on your team — quite possibly you — will stare at a commit titled “fix stuff” and have no idea what it changed or why. Commit messages are the smallest documentation a project has, and the most-read. This guide gives you a format that takes seconds to follow, examples of good and bad messages, and the reasoning that makes a project’s history actually useful.
Good commit messages are one leg of a healthy documentation habit; the other two are a README developers actually read and a changelog users actually read.
The short version: a format that works
A commit message has two parts — a subject line and an optional body — separated by a blank line:
Capitalized, imperative subject of 50 characters or less
Body: explain WHAT changed and WHY, wrapped at 72 characters.
The body is where the reasoning lives: what problem existed,
what this change does about it, and anything a future reader
needs before touching this code again.
Five rules carry almost all of the value:
- Keep the subject to about 50 characters. It stays readable in
git log --oneline, in GitHub lists and in email subjects. - Write the subject in the imperative mood. “Add password reset” — not “Added”, “Adds” or “Adding”. A useful test: the subject should complete the sentence “If applied, this commit will ___.”
- Separate subject and body with a blank line. Git tooling depends on it — without it, your whole message becomes the subject in many views.
- Use the body to explain what and why, not how. The diff already shows how. The body is for intent, context, trade-offs and links to the issue being fixed.
- One logical change per commit. A commit that fixes a bug, renames a file and upgrades a dependency cannot be described by any single honest subject line. Split it.
Bad messages, rewritten
| Instead of this | Write this |
|---|---|
| fix bug | Fix totals rounding down on invoices with tax-exempt items |
| updates | Update README install steps for Node 20 |
| final version 2 | Remove deprecated v1 search endpoint |
| asdf | (this commit should not exist — squash it before merging) |
| changed some files | Add rate limiting to the login endpoint |
Notice the pattern: every good version names the thing affected and the change made, in words a teammate who never saw the code can understand.
The body is where the value is
Subject lines get the attention, but bodies prevent the archaeology. A good body answers three questions:
- What was wrong or missing? “Password reset emails were arriving up to an hour late because they shared the marketing queue.”
- What does this change do about it? “Moves reset emails to the transactional queue and adds a priority flag.”
- What should a reader be careful about? “Requires the new queue name in production config before deploy; see the linked issue.”
That is four sentences, and it will save a future debugging session. Link the issue or pull request (“Refs #123”, or your tracker’s format) so the discussion that produced the change is one hop away.
Conventional Commits: a popular structured option
Many teams add a type prefix to the subject, following the Conventional Commits convention:
feat:— a new featurefix:— a bug fixdocs:— documentation onlyrefactor:— a code change that neither fixes a bug nor adds a featuretest:— adding or correcting testschore:— build, tooling and maintenance
Example: feat: add CSV export to reports page. The payoff is mechanical: tools can group history by type, generate changelog drafts automatically, and even suggest version bumps — feat suggests a minor version, fix a patch, and a breaking-change marker a major one, in the spirit of Semantic Versioning. Adopt the convention if that automation helps your team; the five rules above matter more than the prefix.
Team habits that keep history clean
- Agree the convention in the project docs, ideally where contributors already look. A short “Committing” section near your README’s contributing notes beats a wiki page nobody opens.
- Review messages in pull requests the way you review code. A misleading message merged today is misinformation for years.
- Fix messages before merging, not after. Amending or squashing is safe on your own unmerged branch; rewriting history that teammates have already pulled creates real pain. When in doubt, leave the history and write the next message better.
- Keep commits small enough to describe. If you cannot summarize a commit in 50 characters, it is probably two commits wearing a trench coat.
Frequently asked questions
How long should a commit message be?
The subject: about 50 characters. The body: as long as the reasoning needs — usually a short paragraph or a few bullets, wrapped around 72 characters per line.
Should every commit have a body?
No. A one-line subject is fine for a change whose subject fully explains it (“Fix typo in install instructions”). Reach for the body whenever the why is not obvious from the diff.
What tense and mood should I use?
Imperative present: “Add”, “Fix”, “Remove”. Git itself uses it (“Merge branch…”), and it keeps subjects short and consistent.
Can I fix a bad message after pushing?
On a branch only you use, yes — amend and force-push carefully. On shared history, prefer not to rewrite; correct the record in the pull request description or a follow-up commit note instead.
Do commit messages really matter for a solo project?
Yes — your future self is the teammate. Solo histories are read during exactly the same “why did I do this?” moments, with nobody else to ask.
Bottom line
A good commit message is a 50-character imperative subject, a blank line, and a body that explains what changed and why. It takes under a minute longer than “fix stuff”, and it pays that minute back the first time anyone — including you — has to understand the project’s past. Pair the habit with a maintained changelog and your project’s history stops being a mystery and starts being documentation.







