How to Standardize Commit Messages with Conventional Commits
Introduction
Some time ago, I started thinking about ways to standardize the commit messages I was writing. I was motivated by readability and the need to easily understand what each commit was about.
This brought some challenges but many benefits—such as maintaining focus on commits that solve a single problem, involve fewer files, or touch only one context of the application.
While researching solutions and ways to write good commits, I came across the Conventional Commits standard.
What are Conventional Commits?
Conventional Commits is a lightweight convention for commit messages that follows a specific structure. It defines a format for commit messages that includes a type, an optional scope, and a description.
Structure:
<type>[optional scope]: <description>
Practical example:
feat: add new feature
fix: fix bug in code
Types
Each <type> defines what kind of change was made within that commit. This type should always follow the same pattern, such as feat. Ideally, it should always be written in this same format, without leaving it open for other team members to write feature, for example.
The most common types are:
feat: for new features.fix: for bug fixes.hotfix: for critical failure fixes.refactor: for code refactoring.docs: for documentation changes.test: for adding or correcting tests.chore: for maintenance tasks, such as updating dependencies or tool configuration.perf: for performance improvements.build: for changes that affect the build system or external dependencies.revert: to revert a previous commit.ci: for changes related to continuous integration and deployment.tag: for creating version tags.construction: to indicate work in progress.
However, you and your team can define other types like merge, release, or any other that makes sense within the project.
Scope (optional)
In Conventional Commits messages, the scope is not mandatory but can be used to identify the part of the code or functionality being modified or affected.
For example, if you are working specifically on the login part of the API, you can set the scope as (auth) or (login), depending on what you or your team are used to calling it.
This makes it easier to read and understand that a specific commit might impact something another person is also working on.
Description
The description of each commit should always be clear and concise, stating exactly what was worked on. In some automations, it is possible to add the task or issue ID for identification.
Always analyze the code you are committing and write this description in an easy-to-understand way. This can help when reverting a commit, understanding what was done without reading the code, or even generating an automatic changelog.
Other Uses and Conventions
Identifying Breaking Changes
It is possible to identify in the standardized commit message if it contains a breaking change—that is, a change that breaks compatibility with previous versions.
To do this, use the ! notation after the <type> or <scope>.
Example:
feat!: add new feature that breaks compatibility
Another way to do this, for those who prefer longer commits, is using the BREAKING CHANGE description in uppercase, for example:
feat: add new feature
BREAKING CHANGE: this change breaks compatibility with previous versions
Multi-paragraph Commits
In some cases, it is necessary to write a longer commit message with more details about the changes made.
Example:
fix: prevent racing of requests
Introduce a request id and a reference to latest request. Dismiss
incoming responses other than from latest request.
Remove timeouts which were used to mitigate the racing issue but are
obsolete now.
Reviewed-by: Z
Refs: #123
Emojis
In addition to following the Conventional Commits standard, I decided to bring a more visual element to commit messages by using emojis.
Analyzing some patterns online, I noticed that emojis were used for this purpose, and I started adopting them in projects along with conventional commits.
In practice, I always place the emoji before the <type>, and the emoji is always related to the <type>.
For example, for the fix type, I use the bug emoji -> 🐛. If it is feat, I use ✨, and so on.
This works well for me because, with just a glance, I know what that commit is about—whether I need to pay special attention to a hotfix or if it's just a documentation change.
Project Documentation
After deciding to use this pattern in my projects, it was essential to create documentation to ensure the same pattern is used across all commits and to easily find the emojis related to each type.
Below is the table of emojis and commits that I follow:
| Type | Emoji | Commit Example |
| :--- | :---: | :--- |
| **feat** | ✨ | `:sparkles: feat(auth): Implement login system Task-123` |
| **fix** | 🐛 | `:bug: fix(api): Fix request timeout error Task-456` |
| **docs** | 📚 | `:books: docs: Update README with deploy instructions Task-789` |
| **style** | 🎨 | `:art: style(components): Apply design system to buttons Task-101` |
| **refactor** | ♻️ | `:recycle: refactor(utils): Simplify validation function Task-202` |
| **test** | 🧪 | `:test_tube: test(auth): Add tests for login Task-303` |
| **chore** | 🔧 | `:wrench: chore: Update project dependencies Task-404` |
| **perf** | ⚡ | `:zap: perf(database): Optimize user queries Task-505` |
| **ci** | 🧱 | `:bricks: ci: Configure auto-deploy to staging Task-606` |
| **hotfix** | 💥 | `:boom: hotfix: Fix critical payment failure Task-707` |
| **build** | 📦 | `:package: build: Configure webpack for production Task-808` |
| **revert** | ⏪ | `:rewind: revert: Revert changes in Header component Task-909` |
| **tag** | 🔖 | `:bookmark: tag: Create version tag v1.2.0 Task-910` |
| **construction** | 🚧 | `:construction: construction: Work in progress on dashboard Task-911` |
| **Merge** | 🔀 | `:twisted_rightwards_arrows: Merge: default merge message` |
With this documented pattern, it becomes easy to get the desired emoji for the commit with the correct type.
This way, commits are standardized, easy to read and understand, and more visual, which helps quickly identify the type of change made without having to read the full commit description.
Conclusion
Having a commit pattern is very useful in any project, whether small or large. It helps maintain organization and readability in the commit history, besides facilitating collaboration between team members.
It makes sense to analyze your scope and understand which pattern fits best.
Once a pattern is defined, I recommend documenting it; without documentation, it is easier to drift away and go back to writing commits the old way.
If you have any suggestions for improving this article, feel free to contact me on LinkedIn! :)