A human-readable, machine-parseable standard for writing git commit messages. Based on the Conventional Commits 1.0.0 specification.
Why Conventional Commits?
Most developers write commit messages like this:
fixed stuff update wip changes ok now it works final final FINAL
This is useless. Three months later — or to a new contributor — these messages tell you nothing.
Conventional Commits solves this by giving commit messages a consistent, meaningful structure that:
- Makes
git logactually readable at a glance - Enables automatic changelog generation
- Enables automatic semantic versioning
- Makes code reviews faster — reviewers understand intent before reading the diff
- Helps with bisecting bugs (
git bisectworks better with descriptive history) - Is parseable by CI/CD tools and release automation
- Is the standard used by major open source projects: Angular, Vue, Electron, ESLint, and thousands more
The Full Format
<type>(<scope>): <description> [optional body] [optional footer(s)]
| Part | Required | Description |
|---|---|---|
| type | ✅ Yes | The category of change |
| scope | ❌ No | The area of the codebase affected |
| description | ✅ Yes | A short summary of the change |
| body | ❌ No | Detailed explanation of what and why |
| footer | ❌ No | Breaking changes, issue references, co-authors |
Rules
- A blank line must separate the description from the body
- A blank line must separate the body from footers
- The
!symbol after type/scope signals a breaking change - Multiple footers are allowed, one per line
Types — Complete Reference
feat — New Feature
Use when you add new functionality visible to the end user or API consumer.
feat: add dark mode toggle feat(auth): add OAuth2 login support feat(api): add pagination to /users endpoint
Triggers: MINOR version bump in semantic versioning.
fix — Bug Fix
Use when you fix something that was broken or behaving incorrectly.
fix: resolve crash on empty search input fix(cart): correct total price calculation fix(mobile): fix nav menu not closing on tap
Triggers: PATCH version bump in semantic versioning.
chore — Maintenance
Use for tasks that don't modify source code behavior or tests. Common for dependency updates, config tweaks, removing dead files.
chore: update dependencies chore: remove unused imports chore: clean up .gitignore chore(deps): bump lodash from 4.17.20 to 4.17.21
Triggers: No version bump.
docs — Documentation
Use when only documentation files change. This includes README, inline code comments, JSDoc, wikis, changelogs.
docs: add installation guide to README docs(api): document rate limiting behavior docs: fix broken links in CONTRIBUTING.md
Triggers: No version bump.
style — Code Style / Formatting
Use for changes that do not affect the meaning of the code. Whitespace, formatting, missing semicolons, import ordering — anything a linter or formatter would touch.
style: format files with prettier style(button): fix inconsistent spacing style: remove trailing whitespace
feat or fix for those.Triggers: No version bump.
refactor — Code Restructuring
Use when you rewrite or restructure code without changing its external behavior or fixing a bug.
refactor: extract validation into separate module refactor(auth): simplify token refresh logic refactor: replace forEach with map in data transform
Triggers: No version bump.
test — Tests
Use when adding, updating, or fixing tests. No production code changes.
test: add unit tests for useAnimeList hook test(api): add integration tests for /search endpoint test: fix flaky timeout in auth tests
Triggers: No version bump.
perf — Performance
Use when you make a change that improves performance without changing behavior.
perf: debounce search input to reduce API calls perf(images): add lazy loading to anime cards perf: memoize expensive genre filter computation
Triggers: PATCH version bump in some configurations.
ci — Continuous Integration
Use for changes to CI/CD configuration files and scripts.
ci: add automated deployment to Vercel ci: fix failing lint step in GitHub Actions ci(docker): update Node.js base image to 20-alpine
build — Build System / Dependencies
Use for changes that affect the build system, build tools, or external dependencies.
build: migrate from CRA to Vite build: add path aliases to tsconfig build(deps): add recharts as dependency
revert — Reverting a Commit
Use when reverting a previous commit. The body should include the hash of the reverted commit.
revert: revert "feat: add experimental offline mode" Reverts commit a1b2c3d. Caused a regression in the login flow on Safari.
Extended / Less Common Types
| Type | When to use |
|---|---|
| security | Patching a security vulnerability |
| wip | Work in progress — should never stay on main |
| hotfix | Urgent production fix |
| i18n | Internationalization and localization changes |
| a11y | Accessibility improvements |
| infra | Infrastructure changes (Terraform, Docker, K8s) |
Scopes
A scope is an optional piece in parentheses that narrows what part of the codebase the commit touches.
feat(auth): add JWT refresh logic fix(navbar): resolve dropdown z-index issue chore(deps): update react to 18.3.0
Rules for Scopes
- Lowercase only
- Short and specific — one or two words max
- Consistent — if you use
authonce, always useauth, notauthentication - Optional but recommended in mid-to-large codebases
Common Scope Patterns
# By feature feat(auth): ... feat(chat): ... feat(search): ... # By layer fix(api): ... fix(ui): ... fix(db): ... # By platform fix(mobile): ... fix(web): ... fix(desktop): ...
Description Rules
- Use imperative mood — ✅
add login page/ ❌added login page - Lowercase first letter — ✅
fix null reference in parser/ ❌Fix null reference in parser - No period at the end — ✅
update README/ ❌update README. - Keep it under 72 characters — so it renders cleanly in terminals and GitHub
- Be specific — ✅
fix episode count not updating after progress change/ ❌fix bug
Commit Body
Use the body to explain what changed in more detail, why the change was made, what problem it solves, and any trade-offs or side effects.
feat(chat): wire recommendations through AniList API Previously AniBuddy pulled recommendations from a static local array of 20 hardcoded titles. This meant every user got the same suggestions regardless of what genres they asked for. Now recommendations query the AniList GraphQL API directly, filtering by genre and sorting by popularity. Results are dynamic and always current. Note: adds a 300-500ms delay on recommendation requests due to the API call. Debounced to avoid hammering the endpoint.
Footers
Footers go after the body (separated by a blank line) and follow a Token: value format.
# Referencing Issues Closes #42 Fixes #17 Resolves #88 Related to #55 # Co-authors Co-authored-by: Name <email@example.com> # Breaking Change in Footer BREAKING CHANGE: `exportList` now returns a Promise instead of void.
Full Example with Footers
feat(api): replace REST endpoints with GraphQL The previous REST implementation required multiple round trips to fetch anime details, related series, and character data separately. GraphQL allows all of this in a single query, reducing load time on the details sheet from ~1200ms to ~280ms on average. Closes #34 Co-authored-by: Tahsin Zidane <tahsin@example.com>
Breaking Changes
A breaking change is any change that requires users or consumers of your code to update their own code. There are two ways to signal it:
Method 1 — Exclamation Mark
feat!: change exportList to return Promise feat(api)!: remove deprecated v1 endpoints
Method 2 — Footer
feat(api): update authentication flow BREAKING CHANGE: API tokens are no longer accepted as query params. All requests must include the Authorization header instead.
Both Together (recommended)
feat(api)!: remove query param authentication BREAKING CHANGE: API tokens are no longer accepted as query params. All requests must include the Authorization header instead. Migration guide: https://docs.example.com/migration/v2
Triggers: MAJOR version bump in semantic versioning.
What NOT to Do
Vague descriptions
# Bad fix: fix feat: update chore: stuff fix: oops # Good fix: prevent crash when anime list is empty feat: add episode progress tracking to anime cards chore: remove unused StatusBadge variant
Wrong type
# Bad — this is a fix, not a style change style: fix broken layout on mobile # Bad — this is a feat, not a refactor refactor: add search history dropdown # Good fix: fix broken layout on mobile feat: add search history dropdown
Too many changes in one commit
# Bad — this should be 3 commits feat: add dark mode, fix mobile nav, update README # Good — three separate commits feat: add dark mode toggle fix(nav): fix mobile menu not closing on route change docs: update README with dark mode usage
Versioning & Semantic Release
Conventional Commits maps directly to Semantic Versioning (MAJOR.MINOR.PATCH):
| Commit Type | Version Bump | Example |
|---|---|---|
| feat | MINOR — 1.0.0 → 1.1.0 | New feature added |
| fix | PATCH — 1.0.0 → 1.0.1 | Bug fixed |
| perf | PATCH — 1.0.0 → 1.0.1 | Performance improved |
| BREAKING CHANGE / ! | MAJOR — 1.0.0 → 2.0.0 | API changed |
| Everything else | No bump | Docs, chores, style, etc. |
Tools & Integrations
Changelog Generation
| Tool | Description |
|---|---|
| conventional-changelog | Generate changelogs from commit history |
| git-cliff | Highly configurable changelog generator |
| release-please | Google's automated release PR tool |
Commit Message Linting
| Tool | Description |
|---|---|
| commitlint | Lint commit messages against a ruleset |
| commitizen | Interactive CLI for writing commit messages |
Commit Message Linting Setup
Use commitlint to enforce the convention automatically on every commit.
Install
npm install --save-dev @commitlint/cli @commitlint/config-conventional
Configure — commitlint.config.js
export default {
extends: ['@commitlint/config-conventional'],
};
Test it manually
echo "feat: add login page" | npx commitlint # passes echo "added login page" | npx commitlint # fails with error
Git Hooks Setup with Husky
npm install --save-dev husky npx husky init echo "npx --no -- commitlint --edit \$1" > .husky/commit-msg
Now every commit will be linted before it's accepted. If the message doesn't follow the convention, the commit is rejected with a clear error.
Optional: Commitizen for interactive commits
npm install --save-dev commitizen cz-conventional-changelog
Add to package.json:
{
"config": {
"commitizen": {
"path": "cz-conventional-changelog"
}
}
}
Run npx cz instead of git commit to get a guided prompt.
Team Workflow Tips
- Agree on scopes upfront — Define a list of allowed scopes and document them. Consistency matters more than the specific names.
- One logical change per commit — If your description needs the word "and", consider splitting it into two commits.
- Commit often, squash before merging — Work in small WIP commits locally, then squash into clean conventional commits before opening a PR.
- Use the body for context, not summary — The description is what. The body is why. Future maintainers care more about why.
- Reference issues whenever possible —
Closes #42in the footer creates a paper trail between your code changes and the original task. - Enforce with tooling, not discipline — Human discipline fails. Set up commitlint + Husky so the rules are enforced automatically.
Quick Cheatsheet
FORMAT ────────────────────────────────────────────────────── <type>(<scope>): <description> [optional body] [optional footer(s)] TYPES ────────────────────────────────────────────────────── feat New feature → MINOR version bump fix Bug fix → PATCH version bump chore Maintenance, no change → no bump docs Documentation only → no bump style Formatting, no logic → no bump refactor Restructure, no behavior → no bump test Tests only → no bump perf Performance improvement → PATCH version bump ci CI/CD config → no bump build Build system / deps → no bump revert Revert a commit → depends feat! / BREAKING CHANGE → MAJOR version bump DESCRIPTION RULES ────────────────────────────────────────────────────── ✅ Imperative mood → "add feature" not "added feature" ✅ Lowercase start → "fix bug" not "Fix bug" ✅ No period at end → "update config" not "update config." ✅ Under 72 chars → keep it short and specific FOOTER TOKENS ────────────────────────────────────────────────────── Closes #42 Fixes #17 BREAKING CHANGE: description of what broke Co-authored-by: Name <email> EXAMPLES ────────────────────────────────────────────────────── feat: add anime search by genre feat(chat): wire recommendations through AniList API fix: resolve crash on empty input fix(anilist): handle null description gracefully chore: remove lovable-tagger boilerplate chore(deps): bump vite from 5.4.0 to 5.4.19 docs: add setup instructions to README style: format files with prettier refactor: extract query strings into constants test: add unit tests for useAnimeList hook perf: debounce search input to reduce API calls ci: add GitHub Actions deploy workflow build: migrate bundler from webpack to vite revert: revert "feat: add experimental offline mode" feat!: rename AnimeStatus values to uppercase
Further Reading
- conventionalcommits.org — Official specification
- semver.org — Semantic versioning spec
- commitlint.js.org — Linting rules and config
- semantic-release docs — Automated release pipeline
- Angular commit guidelines — Where the convention originated