Skip to main content

Commit Lint & Conventional Commits

Commit Lint

Commit linting helps enforce consistent and readable commit messages, making project history easier to understand and automate.

This guide follows commitlint-config-conventional, which is based on the Angular commit convention.


✍️ Commit Message Format

A typical commit message looks like this:


<type>(optional scope): <short description>


✅ Real-World Examples


chore: run tests on travis ci
fix(server): send cors headers
feat(blog): add comment section


🏷️ Common Commit Types

Below are the most common commit types supported by
commitlint-config-conventional:

  • build — Changes that affect the build system or dependencies
  • chore — Maintenance tasks that don’t modify production code
  • ci — Changes to CI configuration or scripts
  • docs — Documentation-only changes
  • feat — A new feature
  • fix — A bug fix
  • perf — Performance improvements
  • refactor — Code changes that neither fix a bug nor add a feature
  • revert — Reverts a previous commit
  • style — Formatting changes (no code behavior change)
  • test — Adding or updating tests

🧠 Why Use Commit Lint?

  • Improves readability of Git history
  • Enables automated changelogs
  • Helps teams stay consistent
  • Works well with semantic versioning

🤖 Commitizen (Gitmoji + Conventional Commits)

Commitizen (cz-cli) with the cz-git adapter gives an interactive prompt that walks you through building a commit message, so you don't have to remember the format by hand. It produces commits like:

:emoji: <type>: <subject>

e.g. :bug: fix: fix cz-git configuration

Project-local setup

Two files, kept deliberately separate: package.json only tells Commitizen where the adapter lives, and commitlint.config.js holds cz-git's actual prompt/style options. Splitting them this way avoided CLI resolution errors on Windows when everything lived in package.json.

package.json
{
"scripts": {
"commit": "cz"
},
"devDependencies": {
"commitizen": "^4.3.2",
"cz-git": "^1.13.1"
},
"config": {
"commitizen": {
"path": "node_modules/cz-git"
}
}
}
commitlint.config.js
module.exports = {
rules: {
// standard commitlint rules go here if you want them enforced
},
prompt: {
useEmoji: true,
emojiAlign: 'left',
},
};

Usage: stage changes as normal (git add <files>), then run npm run commit instead of git commit. It walks you through several prompts in order, then shows the composed message for confirmation before committing.

Global setup (works in any repo, no per-project install)

# Install once, globally
npm install -g commitizen cz-git
# Create ~/.czrc so `git cz` works anywhere
Set-Content -Path "$HOME\.czrc" -Value '{
"path": "cz-git",
"useEmoji": true,
"emojiAlign": "left"
}'

Once .czrc exists in the home directory, git cz works in any repo on the machine — no local cz-git install or config file needed.

Walking through the prompts

Verified working example run:

PS C:\Users\kokok\Downloads\cz-emoji-demo> git cz
cz-cli@4.3.2, cz-git@1.13.1

? Select the type of change that you're committing: ci: Changes to our CI configuration files and scripts
? Denote the SCOPE of this change (optional): empty
? Write a SHORT, IMPERATIVE tense description of the change: add .gitignore
? Provide a LONGER description of the change (optional). Use "|" to break new line:
? Select the ISSUES type of change (optional): skip

###--------------------------------------------------------###
:ferris_wheel: ci: add .gitignore
###--------------------------------------------------------###

? Are you sure you want to proceed with the commit above? Yes
[main 26b9e16] :ferris_wheel: ci: add .gitignore
1 file changed, 1 insertion(+)
create mode 100644 .gitignore

Two of these prompts trip people up the first time through, since neither is required and it's not obvious what they're asking for:

  • ? Denote the SCOPE of this change (optional): — the (scope) part of type(scope): subject (see "Commit Message Format" above). It names which part of the codebase the commit touches — a package, module, folder, or feature area, e.g. server, blog, auth, api. It's there so a changelog or git log reader can tell at a glance where a change landed without opening the diff. Press Enter with nothing typed to leave it out — that's what "empty" means in the example above. Skipping it is completely normal for changes that don't belong to one specific area (repo-wide config, CI, tooling), which is exactly the case in the .gitignore example.
  • ? Select the ISSUES type of change (optional): — lets you link the commit to an issue tracker entry (GitHub Issues, Jira, Linear, etc.), producing a trailer like Closes #123 or Refs #123 at the bottom of the commit message. cz-git offers a few link types here, generally something like closed (closes/resolves the issue) or related (just references it, doesn't close it). Selecting "skip" — the default — means the commit isn't linked to any issue at all, which is the right call if you're not using an issue tracker, or this particular commit doesn't correspond to a tracked ticket. There's nothing to configure ahead of time to make "skip" valid; it's always a safe choice.

In short: both prompts are safe to leave empty/skip unless you specifically want the extra metadata, and doing so (as in the example run) produces a perfectly valid conventional commit.

Where this fits with the rest of the stack

This pairs naturally with commitlint (enforcing the conventional-commit shape on a commit-msg git hook, via husky) and with a lint-staged setup — cz-git produces well-formed messages interactively, while commitlint + a git hook is what actually enforces the format for commits that don't go through cz.

Note: git cz fails silently-ish with "No files added to staging! Did you forget to run git add?" if nothing is staged first — easy to forget since it doesn't look like an error at first glance.


🤖 AI-Assisted Commit Messages (Gitmoji + Conventional Commits)

If you're using an AI assistant (e.g. a Claude Code hook, a Git alias, or an editor extension) to draft commit messages automatically, you can prompt it to follow the same Gitmoji + Conventional Commits style used by cz-git above. A typical system prompt for this looks like:

Format:

<emoji> <type>: <summary>

Requirements given to the model:

  • Start with exactly one Gitmoji emoji.
  • Follow with a valid Conventional Commit type.
  • Write a concise summary in imperative mood (present tense).
  • Keep the summary under 72 characters whenever possible.
  • Don't end the summary with a period.
  • Don't include issue numbers, scope, body, footer, explanations, markdown, quotes, or code fences.
  • Return only the final commit message — no preamble.
  • When multiple categories apply, choose the emoji that best represents the primary purpose of the change.

Gitmoji → Commit Type Mapping

EmojiType(s)Meaning
🎨styleImprove structure or formatting
⚡️perfImprove performance
🔥refactor | choreRemove code or files
🐛fixFix a bug
🚑️fixCritical hotfix
featIntroduce new features
📝docsAdd or update documentation
🚀chore | buildDeploy-related changes
💄styleUpdate UI or styling
🎉choreInitial project setup
testAdd, update, or pass tests
🔒️fix | secSecurity or privacy fixes
🔐choreAdd or update secrets
🔖chore | releaseRelease or version tags
🚨style | fixFix compiler or linter warnings
🚧wipWork in progress
💚ciFix CI build
⬇️chore | depsDowngrade dependencies
⬆️chore | depsUpgrade dependencies
📌chore | depsPin dependency versions
👷ciAdd or update CI configuration
📈feat | choreAdd or update analytics
♻️refactorRefactor code
chore | depsAdd a dependency
chore | depsRemove a dependency
🔧choreAdd or update configuration files
🔨choreAdd or update development scripts
🌐feat | i18nInternationalization/localization
✏️docs | styleFix typos
💩refactorImprove poor code
⏪️revertRevert changes
🔀mergeMerge branches
📦️build | choreAdd or update compiled files or packages
👽️fix | refactorAdapt to external API changes
🚚refactor | choreMove or rename files, paths, or routes
📄choreAdd or update license
💥feat! | fix!Introduce breaking changes
🍱chore | styleAdd or update assets
♿️accessibility | styleImprove accessibility
💡docsAdd or update source code comments
🍻feat | refactorExperimental or playful coding
💬feat | docsAdd or update text and literals
🗃️db | choreDatabase-related changes
🔊choreAdd or update logs
🔇choreRemove logs
👥choreAdd or update contributors
🚸ux | featImprove user experience
🏗️arch | refactorArchitectural changes
📱style | featResponsive design
🤡testAdd or update mocks
🥚featAdd or update an easter egg
🙈choreAdd or update .gitignore
📸testAdd or update snapshots
⚗️experiment | featExperiments or prototypes
🔍️seo | featImprove SEO
🏷️types | featAdd or update types
🌱db | choreAdd or update seed files
🚩featAdd, update, or remove feature flags
🥅fix | refactorCatch errors
💫style | featAdd or update animations or transitions
🗑️refactor | choreDeprecate code pending cleanup
🛂feat | secAuthorization, roles, or permissions
🩹fixSmall non-critical fix
🧐chore | testData inspection or exploration
⚰️refactorRemove dead code
🧪testAdd a failing test
👔featAdd or update business logic
🩺feat | choreAdd or update health checks
🧱infra | choreInfrastructure changes
🧑‍💻dx | choreImprove developer experience
💸feat | choreSponsorship or financial infrastructure
🧵refactor | perfMultithreading or concurrency
🦺feat | fixValidation-related changes
✈️featImprove offline support
🦖refactorAdd backwards compatibility

Example outputs

✨ feat: add OAuth login
🐛 fix: prevent null pointer when loading settings
♻️ refactor: simplify authentication flow
📝 docs: update installation guide
⚡️ perf: optimize image loading
🔧 chore: update eslint configuration
🗃️ db: add user preferences table
🦺 feat: validate email addresses before saving

  • Gitmoji — An interactive guide to using emojis on git commit messages
  • commitlint
  • Commitizen (cz-cli + cz-git)
  • Husky
  • Conventional Commits
  • Semantic Release