Skip to main content

Command Palette

Search for a command to run...

Commitlint Standards: Enforce Commit Messages

Updated
7 min readView as Markdown
T

Welcome to TopperBlog! 👋

I'm a tech content creator passionate about helping developers level up their careers and master cutting-edge technologies.

🎯 What I Write About: • AI/ML Engineering & LLMs • Web3 & Blockchain Development
• System Design & Architecture • Interview Preparation (FAANG) • Freelancing & Remote Work • Modern Tech Stacks (Next.js, React, Rust, TypeScript) • Performance Optimization & Best Practices

💼 Mission: Sharing practical, actionable insights that accelerate your tech career and maximize your earning potential.

📚 15+ In-Depth Guides covering everything from earning $10k/month as a freelancer to cracking FAANG interviews.

🌐 Let's connect and grow together in this amazing tech journey!

#TechBlogger #SoftwareEngineering #CareerGrowth #WebDevelopment #AIEngineering

Commitlint Standards: Enforce Commit Messages with Git Hooks

Table of Contents

  1. What is Commitlint?
  2. Why Enforce Commit Message Standards?
  3. Setup and Installation
  4. Configuration Examples
  5. Commit Message Format
  6. Comparison Table
  7. Frequently Asked Questions

What is Commitlint?

Commitlint is a powerful tool that checks if your commit messages meet the conventional commit format. It works seamlessly with Git hooks to enforce commit message standards across your development team, ensuring consistency, readability, and automated changelog generation.

The tool integrates with Husky to create Git hooks that validate commit messages before they're accepted into your repository. This prevents poorly formatted commits from entering your codebase and maintains a clean, professional Git history.

Key Benefits:

  • Automated validation of commit messages
  • Consistent format across team members
  • Better changelog generation from commit history
  • Improved code review process
  • Semantic versioning support

Why Enforce Commit Message Standards?

Enforcing commit message standards isn't just about being pedantic—it provides tangible benefits:

  1. Automated Changelog Generation: Tools can parse structured commits to generate release notes automatically
  2. Better Navigation: Quickly filter commits by type (features, fixes, breaking changes)
  3. Clearer History: Understand what changed and why without reading code
  4. Semantic Versioning: Automatically determine version bumps based on commit types
  5. Team Collaboration: Consistent format reduces cognitive load when reviewing history
  6. CI/CD Integration: Trigger specific workflows based on commit types

Setup and Installation

Prerequisites

  • Node.js (v14 or higher)
  • npm or yarn
  • Git repository initialized

Step-by-Step Installation

Step 1: Install Dependencies

npm install --save-dev @commitlint/{cli,config-conventional} husky

Step 2: Initialize Husky

npx husky-init && npm install

Step 3: Create Commitlint Configuration

echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js

Step 4: Add Commit Message Hook

npx husky add .husky/commit-msg 'npx --no -- commitlint --edit ${1}'

Step 5: Test Your Setup

git commit -m "invalid commit message"  # Should fail
git commit -m "feat: add new feature"   # Should succeed

Configuration Examples

Example 1: Basic Configuration

// commitlint.config.js
module.exports = {
  extends: ['@commitlint/config-conventional'],
  rules: {
    'type-enum': [
      2,
      'always',
      [
        'feat',     // New feature
        'fix',      // Bug fix
        'docs',     // Documentation
        'style',    // Formatting
        'refactor', // Code restructuring
        'test',     // Adding tests
        'chore',    // Maintenance
      ],
    ],
    'subject-case': [2, 'never', ['upper-case']],
    'subject-max-length': [2, 'always', 100],
  },
};

Example 2: Custom Rules Configuration

// commitlint.config.js
module.exports = {
  extends: ['@commitlint/config-conventional'],
  rules: {
    'type-enum': [2, 'always', [
      'feat', 'fix', 'docs', 'style', 'refactor',
      'perf', 'test', 'build', 'ci', 'chore', 'revert'
    ]],
    'scope-enum': [2, 'always', [
      'api', 'ui', 'database', 'auth', 'core'
    ]],
    'scope-empty': [2, 'never'],
    'subject-empty': [2, 'never'],
    'subject-full-stop': [2, 'never', '.'],
    'header-max-length': [2, 'always', 72],
    'body-leading-blank': [2, 'always'],
    'footer-leading-blank': [2, 'always'],
  },
};

Example 3: Monorepo Configuration

// commitlint.config.js
module.exports = {
  extends: ['@commitlint/config-conventional', '@commitlint/config-lerna-scopes'],
  rules: {
    'scope-enum': async (ctx) => {
      const packages = [
        'frontend',
        'backend',
        'shared',
        'mobile',
        'docs'
      ];
      return [2, 'always', packages];
    },
    'type-enum': [2, 'always', [
      'feat', 'fix', 'docs', 'style', 'refactor',
      'test', 'chore', 'deps', 'release'
    ]],
  },
};

Example 4: Advanced with Custom Plugins

// commitlint.config.js
module.exports = {
  extends: ['@commitlint/config-conventional'],
  plugins: ['commitlint-plugin-jira-rules'],
  rules: {
    'jira-task-id-max-length': [2, 'always', 9],
    'jira-task-id-min-length': [2, 'always', 3],
    'jira-task-id-separator': [2, 'always', '-'],
    'type-enum': [2, 'always', [
      'feat', 'fix', 'docs', 'style', 'refactor',
      'perf', 'test', 'build', 'ci', 'chore'
    ]],
    'references-empty': [2, 'never'],
    'header-max-length': [2, 'always', 100],
  },
  parserPreset: {
    parserOpts: {
      issuePrefixes: ['JIRA-', 'PROJ-'],
    },
  },
};

Example 5: Package.json Integration

{
  "name": "my-project",
  "version": "1.0.0",
  "scripts": {
    "commit": "git-cz",
    "commitlint": "commitlint --edit",
    "prepare": "husky install"
  },
  "devDependencies": {
    "@commitlint/cli": "^17.6.0",
    "@commitlint/config-conventional": "^17.6.0",
    "commitizen": "^4.3.0",
    "cz-conventional-changelog": "^3.3.0",
    "husky": "^8.0.3"
  },
  "config": {
    "commitizen": {
      "path": "./node_modules/cz-conventional-changelog"
    }
  },
  "commitlint": {
    "extends": ["@commitlint/config-conventional"],
    "rules": {
      "body-max-line-length": [1, "always", 100],
      "footer-max-line-length": [1, "always", 100]
    }
  }
}

Commit Message Format

Conventional Commit Structure

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

<body>

<footer>

Type Values

  • feat: A new feature for the user
  • fix: A bug fix for the user
  • docs: Documentation changes
  • style: Code style changes (formatting, semicolons, etc.)
  • refactor: Code changes that neither fix bugs nor add features
  • perf: Performance improvements
  • test: Adding or updating tests
  • build: Changes to build system or dependencies
  • ci: Changes to CI configuration files
  • chore: Other changes that don't modify src or test files
  • revert: Reverts a previous commit

Examples of Good Commit Messages

feat(auth): add OAuth2 authentication support

fix(api): resolve null pointer exception in user service

docs(readme): update installation instructions

style(components): format code according to prettier rules

refactor(database): optimize query performance

perf(images): implement lazy loading for gallery

test(auth): add unit tests for login functionality

build(deps): upgrade react to version 18.2.0

ci(github): add automated deployment workflow

chore(release): bump version to 2.0.0

Breaking Changes

feat(api)!: redesign REST API endpoints

BREAKING CHANGE: API endpoints now use /v2/ prefix
All clients must update their base URL configuration

Comparison Table

FeatureCommitlintGit Commit TemplateManual ReviewCommitizen
Automated Enforcement✅ Yes❌ No❌ No✅ Yes
Pre-commit Validation✅ Yes❌ No❌ No⚠️ Optional
Custom Rules✅ Extensive⚠️ Limited✅ Yes⚠️ Limited
Team Consistency✅ High⚠️ Medium❌ Low✅ High
Learning Curve⚠️ Medium✅ Low✅ Low⚠️ Medium
CI/CD Integration✅ Easy❌ Difficult❌ Difficult✅ Easy
Changelog Generation✅ Automatic❌ Manual❌ Manual✅ Automatic
Setup Complexity⚠️ Medium✅ Simple✅ None⚠️ Medium
IDE Support✅ Good✅ Good✅ Native✅ Good
Cost✅ Free✅ Free✅ Free✅ Free
Maintenance⚠️ Low✅ None⚠️ High⚠️ Low
Error Messages✅ Detailed❌ None⚠️ Varies✅ Interactive

Frequently Asked Questions

Q1: Can I bypass commitlint for emergency fixes?

A: Yes, you can use the --no-verify flag to skip Git hooks:

git commit -m "emergency fix" --no-verify

However, this should be used sparingly and only in genuine emergencies. Consider creating a proper commit message afterward with git commit --amend.

Q2: How do I handle commits from external contributors?

A: You have several options:

  1. CI/CD Validation: Add commitlint to your CI pipeline to check all commits
  2. Documentation: Provide clear contribution guidelines
  3. Squash Merging: Squash external PRs and write proper commit messages
  4. Bot Integration: Use GitHub bots to validate PR commit messages

Q3: What if my team disagrees on commit message format?

A: Commitlint is highly configurable. Hold a team meeting to:

  • Discuss pain points with current practices
  • Review conventional commit benefits
  • Customize rules to fit your workflow
  • Start with lenient rules and gradually tighten them
  • Document decisions in your contributing guide

Q4: Can commitlint work with existing projects?

A: Absolutely! Commitlint only validates new commits. To implement:

  1. Install and configure commitlint
  2. Announce the change to your team
  3. Provide training and documentation
  4. Consider a grace period with warnings instead of errors
  5. Optionally rewrite history (use with caution)

Q5: How do I integrate commitlint with CI/CD?

A: Add this to your CI configuration:

GitHub Actions:

- name: Validate commits
  run: npx commitlint --from=HEAD~1 --to=HEAD --verbose

GitLab CI:

commitlint:
  script:
    - npx commitlint --from=$CI_COMMIT_BEFORE_SHA --to=$CI_COMMIT_SHA

Q6: What's the difference between commitlint and commitizen?

A: They serve complementary purposes:

  • Commitlint: Validates commit messages (enforcer)
  • Commitizen: Helps write commit messages (assistant)

Use both together for the best experience: Commitizen guides users in writing proper commits, while commitlint ensures compliance.

Q7: Can I use commitlint with other Git hooks?

A: Yes! Husky allows multiple hooks. Example .husky/commit-msg:

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx --no -- commitlint --edit ${1}
# Add other validations here

Q8: How do I handle long commit messages?

A: Use the body section for detailed explanations:

git commit -m "feat(api): add user authentication" -m "
- Implement JWT token generation
- Add password hashing with bcrypt
- Create middleware for route protection
- Add refresh token mechanism
"

Or use an editor:

git commit  # Opens default editor

Conclusion

Implementing commitlint with Git hooks transforms your development workflow by enforcing consistent, meaningful commit messages. While there's an initial setup investment, the long-term benefits—automated changelogs, better collaboration, and clearer project history—far outweigh the costs.

Start with the basic configuration, customize rules to fit your team's needs, and gradually adopt best practices. Your future self (and teammates) will thank you for maintaining a clean, professional Git history.

Next Steps:

  1. Install commitlint in your project today
  2. Customize rules for your team's workflow
  3. Document your commit message standards
  4. Train team members on the new process
  5. Monitor and refine your configuration over time

Happy committing! 🚀