DEV Community

Cover image for Git Commit Messages Like a Pro: A Complete Guide to feat, fix, chore, init and More
Moshfiqur Rahman
Moshfiqur Rahman

Posted on Edited on

Git Commit Messages Like a Pro: A Complete Guide to feat, fix, chore, init and More

TL;DR: Use type(scope): subject to write clear commit messages. Choose the right type for each change, keep commits small, and commit when one task is complete. This makes your Git history easier to read and your work easier to manage.

The Git History Nobody Wants to Read

A few months ago, I opened the Git history of an old project to find out when a bug was introduced.

Here is what I found:

final
final_v2
update
fixed stuff
asdf
final_final_REAL
Enter fullscreen mode Exit fullscreen mode

The problem? I could not understand what I had changed in any of those commits.

I think many developers have done this at some point. I have done it too.

When we are working on a project, writing update or fixed stuff may feel easy. But after a few months, those messages become useless.

This is why writing good commit messages matters.

A good commit message tells other developers what changed and why. It also helps you understand your own work when you return to a project later.

Let's look at how to write better Git commit messages.

1. What Are Conventional Commits?

Conventional Commits is a simple way to write Git commit messages using a common format.

Instead of writing messages in different styles, we follow one structure.

<type>(<scope>): <subject>

<body>

<footer>
Enter fullscreen mode Exit fullscreen mode

Here is a simple example:

git commit -m "feat(auth): add Google login"
Enter fullscreen mode Exit fullscreen mode

Let's understand why this format is useful.

  • Everyone can understand the commit history more easily.
  • Developers can find changes faster.
  • Code reviews become easier.
  • Tools can create changelogs from commit messages.
  • Release tools can use commit messages to decide version numbers.

It also makes teamwork easier because everyone follows the same format.

2. Understanding the Commit Message Structure

Let's break down this example:

feat(auth): add Google login
Enter fullscreen mode Exit fullscreen mode

There are three main parts.

Type: feat

It tells us what kind of change was made.

Scope: auth

It tells us which part of the project was changed. The scope is optional.

Subject: add Google login

It gives a short description of the change.

We can also add a body and footer when needed.

Body

The body explains why the change was made or gives more details.

Footer

The footer can reference an issue or describe a breaking change.

A complete example:

feat(auth): add Google login

Allow users to sign in using their Google accounts.
This makes the login process easier for new users.

Closes #42
Enter fullscreen mode Exit fullscreen mode

You do not need a body and footer for every commit. Use them when they add useful information.

3. Git Commit Types Explained

This is one of the most important parts of writing good commit messages.

Different types describe different kinds of changes.

Let's go through the most commonly used types and see when to use them.

3.1. init

Use init when starting a new project or creating the initial project setup.

Example:

git commit -m "init: set up Node.js project"
Enter fullscreen mode Exit fullscreen mode

Other examples:

init: create Next.js application
init: add initial project structure
init: configure TypeScript
Enter fullscreen mode Exit fullscreen mode

When should you use it?

Use it when you are setting up a new project for the first time.

Note that init is a common team convention, not a required Conventional Commits type. You can also use chore or another suitable type for initial setup.

3.2. feat

Use feat when adding a new feature to an application.

For example:

  • Adding Google login.
  • Creating a new API endpoint.
  • Adding a search feature.
  • Adding a new dashboard page.
  • Adding payment integration.

Example:

git commit -m "feat(auth): add Google login"
Enter fullscreen mode Exit fullscreen mode

Other examples:

feat(user): add profile update feature
feat(api): add product search endpoint
feat(payment): integrate Stripe checkout
Enter fullscreen mode Exit fullscreen mode

When should you use it?

Use feat whenever you add new functionality that users or other parts of the application can use.

3.3. fix

Use fix when correcting a bug or something that does not work as expected.

For example:

  • Login fails with valid credentials.
  • Product quantity becomes negative.
  • API returns incorrect data.
  • Payment status is not updated.
  • Form validation does not work correctly.

Example:

git commit -m "fix(cart): prevent negative quantity"
Enter fullscreen mode Exit fullscreen mode

Other examples:

fix(auth): handle expired access tokens
fix(api): return correct user data
fix(payment): prevent duplicate transactions
Enter fullscreen mode Exit fullscreen mode

When should you use it?

Use fix when the main purpose of your change is to correct existing behavior.

3.4. docs

Use docs when changing project documentation.

For example:

  • Updating README.
  • Adding installation instructions.
  • Writing API documentation.
  • Updating project guides.
  • Correcting documentation errors.

Example:

git commit -m "docs(readme): add installation steps"
Enter fullscreen mode Exit fullscreen mode

Other examples:

docs(api): add authentication examples
docs(project): update setup guide
docs(readme): add environment variables
Enter fullscreen mode Exit fullscreen mode

When should you use it?

Use docs when you are changing documentation rather than application functionality.

3.5. style

Use style for code formatting changes that do not change how the application works.

For example:

  • Fixing indentation.
  • Removing unnecessary spaces.
  • Formatting code with Prettier.
  • Changing semicolon usage.
  • Organizing whitespace.

Example:

git commit -m "style(ui): format components with prettier"
Enter fullscreen mode Exit fullscreen mode

Other examples:

style(api): fix code indentation
style(auth): apply consistent formatting
style(project): format TypeScript files
Enter fullscreen mode Exit fullscreen mode

Important: style usually refers to code formatting, not visual changes to a website.

If you change a button's color or add a new UI feature, use a suitable type such as feat or fix.

3.6. refactor

Use refactor when improving the code structure without changing its existing behavior.

For example:

  • Moving repeated logic into reusable functions.
  • Separating business logic from controllers.
  • Improving folder structure.
  • Extracting validation middleware.
  • Removing unnecessary code duplication.

Example:

git commit -m "refactor(api): extract validation middleware"
Enter fullscreen mode Exit fullscreen mode

Other examples:

refactor(auth): simplify token validation
refactor(user): separate service logic
refactor(database): improve query structure
Enter fullscreen mode Exit fullscreen mode

When should you use it?

Use refactor when the code structure changes but the application's external behavior remains the same.

3.7. perf

Use perf when improving application performance.

For example:

  • Optimizing database queries.
  • Reducing API response time.
  • Adding database indexes.
  • Improving caching.
  • Reducing unnecessary API requests.

Example:

git commit -m "perf(search): optimize database queries"
Enter fullscreen mode Exit fullscreen mode

Other examples:

perf(api): reduce response processing time
perf(database): add index for user queries
perf(cache): improve Redis caching
Enter fullscreen mode Exit fullscreen mode

When should you use it?

Use perf when your main goal is to make something faster or use fewer resources.

3.8. test

Use test when adding, updating, or fixing tests.

For example:

  • Adding unit tests.
  • Writing integration tests.
  • Updating API test cases.
  • Fixing failing test cases.
  • Adding authentication tests.

Example:

git commit -m "test(auth): add login unit tests"
Enter fullscreen mode Exit fullscreen mode

Other examples:

test(user): add profile update tests
test(api): cover error responses
test(payment): add checkout integration tests
Enter fullscreen mode Exit fullscreen mode

When should you use it?

Use test when the main change is related to tests rather than production code.

3.9. build

Use build when changing the build system, dependencies, or build configuration.

For example:

  • Updating package dependencies.
  • Changing TypeScript configuration.
  • Updating Docker build configuration.
  • Changing Webpack or Vite settings.
  • Updating build scripts.

Example:

git commit -m "build(deps): update React dependencies"
Enter fullscreen mode Exit fullscreen mode

Other examples:

build(docker): update Node.js image
build(config): update TypeScript settings
build(deps): add Zod dependency
Enter fullscreen mode Exit fullscreen mode

3.10. ci

Use ci when changing Continuous Integration or Continuous Delivery configuration.

For example:

  • Adding GitHub Actions.
  • Updating automated test workflows.
  • Changing deployment pipelines.
  • Adding lint checks to CI.
  • Updating build automation.

Example:

git commit -m "ci(github): add test workflow"
Enter fullscreen mode Exit fullscreen mode

Other examples:

ci(actions): run tests on pull requests
ci(deploy): update deployment workflow
ci(project): add lint checks
Enter fullscreen mode Exit fullscreen mode

3.11. chore

Use chore for general project maintenance that does not fit another suitable type.

For example:

  • Updating .gitignore.
  • Cleaning unused configuration files.
  • Organizing project scripts.
  • Updating development tools.
  • General maintenance tasks.

Example:

git commit -m "chore: update gitignore"
Enter fullscreen mode Exit fullscreen mode

Other examples:

chore(deps): update development dependencies
chore(project): remove unused files
chore(config): update environment example
Enter fullscreen mode Exit fullscreen mode

One thing to remember: Do not use chore for every change.

If you add a feature, use feat. If you fix a bug, use fix. If you improve the code structure, use refactor.

Choose the type that best describes the actual change.

3.12. revert

Use revert when undoing a previous commit.

For example, imagine you added a new feature but later found that it caused problems. You may need to revert that commit.

Example:

git commit -m "revert: remove Google login feature"
Enter fullscreen mode Exit fullscreen mode

You can also reference the original commit:

revert: remove Google login feature

This reverts commit abc1234.
Enter fullscreen mode Exit fullscreen mode

When should you use it?

Use revert when you want to record the removal of an earlier change in Git history.

4. Other Commit Types You May See

Apart from the common types above, you may see other commit types in different projects.

These are not all part of the official Conventional Commits type list. Teams may define their own rules.

Type Common use Example
wip Work in progress wip: work on user dashboard
merge Merge-related commit merge: integrate feature branch
release Release preparation release: prepare version 1.2.0
improvement General improvement improvement: improve search experience
init Initial project setup init: create backend project

Use these only when your team has agreed to use them.

For example, wip may be useful on a personal development branch, but it is usually better to keep the main branch history clean.

5. How to Choose the Right Scope

A scope tells us which part of the project was changed.

It makes commit messages easier to understand, especially in large applications.

For example:

git commit -m "feat(auth): add email verification"
git commit -m "fix(payment): handle failed transactions"
git commit -m "refactor(user): extract validation logic"
Enter fullscreen mode Exit fullscreen mode

Here are some common scopes:

Scope Used for
auth Authentication
user User management
payment Payment features
api API-related changes
database Database operations
ui User interface
admin Admin dashboard
notification Notifications
socket Real-time communication
config Configuration

You do not need to use every scope in every project.

Choose scopes that match your project structure and keep them consistent across your team.

Also, remember that the scope is optional.

If a change does not belong to one clear area, you can leave it out.

6. How to Write a Good Commit Subject

The subject is the first line of your commit message.

It should explain the change clearly and briefly.

Here are five simple rules.

1. Use imperative wording

Write:

add user authentication
Enter fullscreen mode Exit fullscreen mode

Avoid:

added user authentication
adding user authentication
Enter fullscreen mode Exit fullscreen mode

Think of it as completing the sentence: "If applied, this commit will..."

2. Keep it short

Try to keep the subject around 50 characters when practical. This is a useful guideline, not a strict Git rule.

3. Avoid unnecessary punctuation

Do not add a full stop at the end.

4. Use clear words

Avoid vague descriptions like update code or fix issue.

5. Describe the actual change

Write what changed, not a long explanation of how you implemented it.

7. When Should You Add a Body or Footer?

Most commits only need a subject line.

But sometimes, the subject is not enough.

That is when a body or footer becomes useful.

Commit body

Use the body to explain why you made the change or provide important details.

Example:

fix(auth): handle expired refresh tokens

Previously, expired refresh tokens were not handled
correctly. This change returns an authentication error
and asks the user to log in again.
Enter fullscreen mode Exit fullscreen mode

Commit footer

Use the footer to reference an issue or describe a breaking change.

Example:

feat(api): add product filtering

Allow users to filter products by category and price.

Closes #42
Enter fullscreen mode Exit fullscreen mode

Breaking changes

A breaking change is a change that may cause existing users or applications to stop working without an update.

You can mark it with !:

feat(api)!: change user response format
Enter fullscreen mode Exit fullscreen mode

Or use a footer:

feat(api): change user response format

BREAKING CHANGE: The API now returns user data
inside a data object instead of returning an array.
Enter fullscreen mode Exit fullscreen mode

Both approaches are supported by Conventional Commits.

8. When Should You Commit Your Code?

This is an important question, especially for developers who are new to Git.

My simple rule is:

Commit when one logical piece of work is complete.

You do not need to wait until the entire feature or project is finished.

You can commit after completing a small, meaningful task.

Good times to commit

1. After completing a small feature

For example, after implementing user registration:

git commit -m "feat(auth): add user registration"
Enter fullscreen mode Exit fullscreen mode

2. After fixing a specific bug

git commit -m "fix(cart): update total after item removal"
Enter fullscreen mode Exit fullscreen mode

3. After completing a refactoring task

git commit -m "refactor(user): separate service logic"
Enter fullscreen mode Exit fullscreen mode

4. After adding or updating tests

git commit -m "test(auth): add registration tests"
Enter fullscreen mode Exit fullscreen mode

5. Before switching to another task

If your current work is in a stable and meaningful state, committing it can help you switch tasks safely.

6. After completing a meaningful development step

For example, you may first create the database model, then add the service logic, and finally connect it to the API.

Each step can have its own commit if it is complete and useful on its own.

When should you avoid committing?

Avoid creating normal commits that contain:

  • Unrelated changes from different tasks.
  • Unnecessary debugging logs.
  • Accidentally included .env files or credentials.
  • Temporary files that should not be tracked.
  • Half-finished changes that break the shared branch.

However, work-in-progress commits are sometimes useful on personal feature branches. The important thing is to keep shared project history understandable.

9. Why Should We Commit Regularly?

Some developers wait until they finish a large feature before creating a commit.

That can make things difficult later.

Here are some reasons to commit regularly.

Easy to understand

Small commits tell you exactly what changed.

Easy to review

Other developers can review smaller changes without going through hundreds of unrelated lines.

Easy to undo

If a particular change causes a problem, you can revert that commit without removing unrelated work.

Easy to debug

Git has a useful command called git bisect. It helps find which commit introduced a bug.

This works best when commits represent clear and complete changes.

Better project history

When you run:

git log --oneline
Enter fullscreen mode Exit fullscreen mode

You should be able to understand how your project has changed over time.

10. Bad vs Good Commit Messages

Let's compare some common examples.

Bad commit message Better commit message
fixed stuff fix(cart): prevent negative quantity
update feat(auth): add Google login
changes refactor(api): extract validation logic
final version docs(readme): update setup guide
bug fixed fix(payment): handle failed transactions
new things feat(user): add profile update
code update style(ui): format React components

The better messages tell us what changed without opening the code.

That is the main goal.

11. Commit Types and Semantic Versioning

Conventional Commits can also help with software version management.

Many projects use Semantic Versioning, which follows this format:

MAJOR.MINOR.PATCH
Enter fullscreen mode Exit fullscreen mode

For example:

1.0.0
Enter fullscreen mode Exit fullscreen mode

Here is how the main commit types relate to version changes when using a compatible release setup.

Commit Version change Example
fix PATCH 1.0.0 to 1.0.1
feat MINOR 1.0.0 to 1.1.0
Breaking change MAJOR 1.0.0 to 2.0.0

A breaking change can be marked using ! or BREAKING CHANGE:.

Tools such as semantic-release and release-please can read commit messages and help generate changelogs and releases.

One important thing: these version changes depend on your release tool and its configuration. Git itself does not automatically increase version numbers based on commit types.

12. How to Follow These Rules Automatically

Remembering all these rules can take time.

Fortunately, there are tools that can check commit messages for you.

commitlint

commitlint checks whether your commit messages follow the selected convention.

Install it:

npm install --save-dev @commitlint/cli @commitlint/config-conventional
Enter fullscreen mode Exit fullscreen mode

Create a configuration file:

// commitlint.config.js

module.exports = {
  extends: ['@commitlint/config-conventional'],
};
Enter fullscreen mode Exit fullscreen mode

If your project uses ES modules, use:

// commitlint.config.js

export default {
  extends: ['@commitlint/config-conventional'],
};
Enter fullscreen mode Exit fullscreen mode

Husky

Husky allows you to run Git hooks automatically.

Install and initialize it:

npm install --save-dev husky

npx husky init
Enter fullscreen mode Exit fullscreen mode

Create a file named .husky/commit-msg with this content:

npx --no -- commitlint --edit "$1"
Enter fullscreen mode Exit fullscreen mode

Now, when you create a commit, commitlint checks the message.

For example:

git commit -m "fixed stuff"
Enter fullscreen mode Exit fullscreen mode

The commit will be rejected if the message does not follow your configured rules.

But this message should pass:

git commit -m "fix(auth): handle expired tokens"
Enter fullscreen mode Exit fullscreen mode

commitizen

If you prefer answering questions instead of writing commit messages manually, you can use Commitizen.

It guides you through the process of choosing a type, scope, and description.

Install it:

npm install --save-dev commitizen cz-conventional-changelog
Enter fullscreen mode Exit fullscreen mode

Then run:

npx cz
Enter fullscreen mode Exit fullscreen mode

It will help you create a properly formatted commit message.

13. Useful Git Commands Before Committing

Before creating a commit, I recommend checking your changes.

Here are some useful commands.

Check changed files:

git status
Enter fullscreen mode Exit fullscreen mode

Review your changes:

git diff
Enter fullscreen mode Exit fullscreen mode

Stage the files you want to commit:

git add .
Enter fullscreen mode Exit fullscreen mode

You can also stage a specific file:

git add src/controllers/auth.controller.ts
Enter fullscreen mode Exit fullscreen mode

Review staged changes:

git diff --cached
Enter fullscreen mode Exit fullscreen mode

Create a commit:

git commit -m "feat(auth): add email verification"
Enter fullscreen mode Exit fullscreen mode

View recent commits:

git log --oneline
Enter fullscreen mode Exit fullscreen mode

View detailed commit history:

git log
Enter fullscreen mode Exit fullscreen mode

One more useful command:

git add -p
Enter fullscreen mode Exit fullscreen mode

It allows you to select specific parts of a file instead of staging the entire file.

This is helpful when one file contains changes from different tasks.

14. Common Commit Mistakes to Avoid

Here are some mistakes developers should try to avoid.

Using chore for everything

Not every change is maintenance. Choose the type that describes the actual work.

Writing very long subject lines

Keep the first line short. Add more details in the body when needed.

Mixing unrelated changes

Do not combine a payment bug fix and a dashboard redesign in one commit.

Committing sensitive information

Never commit passwords, API keys, private tokens, or production credentials.

If a secret has already been pushed, removing it from the latest commit is not enough. You should rotate or revoke that secret immediately.

Committing without checking staged files

Always review what you are about to commit.

Creating commits with unclear messages

Avoid messages such as update, changes, done, and final.

A little extra time spent writing a clear message can save a lot of time later.

15. A Simple Commit Message Cheat Sheet

Here is a quick reference you can keep for your next project.

Type When to use Example
feat New feature feat(auth): add Google login
fix Bug fix fix(cart): prevent negative quantity
docs Documentation docs(readme): update setup guide
style Code formatting style(ui): format components
refactor Code restructuring refactor(api): extract middleware
perf Performance improvement perf(db): optimize queries
test Test changes test(user): add signup tests
build Build and dependencies build(deps): update packages
ci CI/CD changes ci: add GitHub Actions workflow
chore General maintenance chore: update gitignore
revert Undo a previous commit revert: remove login feature

Additional team-specific conventions:

Type When to use
init Initial project setup
wip Work in progress
merge Merge-related commit
release Release preparation
improvement General improvement

16. My Checklist Before Every Commit

Before creating a commit, check these things:

  • [ ] Does this commit contain one logical change?
  • [ ] Did I review the changed files?
  • [ ] Did I check the staged changes?
  • [ ] Did I choose the correct commit type?
  • [ ] Is the commit message clear?
  • [ ] Did I avoid unrelated changes?
  • [ ] Did I exclude passwords and API keys?
  • [ ] Did I run the relevant tests?

You do not need to follow every rule perfectly from day one. Start with clear messages and small commits. Build the habit over time.

Final Thoughts

Writing good commit messages is a small habit, but it makes a real difference when working on software projects.

You do not need to write long messages or use complicated words. Just explain what changed and choose the right commit type.

When you work with other developers, a clean Git history makes everyone's job easier.

Start using feat, fix, docs, refactor, and the other commit types in your next project.

Your future self will thank you.

What is the most unusual commit message you have ever written or seen?

Share it in the comments

Happy coding

Top comments (2)

Collapse
 
mdsharifulislamr profile image
MD Shariful Islam •

impressive and helpful

Collapse
 
iamrakiburrahman profile image
Rakibur Rahman •

wow