Skip to content

How to set up Husky with Conventional Commits, Prettier and ESLint (Husky v9)

Set up Husky v9 to check your code with Prettier and ESLint before every commit, and commitlint to enforce Conventional Commits. Step by step, with current commands.

git git-commit husky eslint prettier

Husky makes Git hooks easy: commands that run automatically before a commit or when you write its message. With it you can stop unformatted code, lint errors or messy commit messages from getting into the repository.

In this note we’ll set up, in this order:

  1. Prettier, to format code.
  2. ESLint, to catch errors.
  3. Husky, to run both before every commit.
  4. commitlint, to enforce Conventional Commits.

This guide uses Husky v9. If you see tutorials with npx husky-init or npx husky add, they’re for older versions: those commands no longer work.

1. Prettier

Install it:

npm install --save-dev --save-exact prettier

Create a .prettierrc config file at the project root. This is the one I use:

{
  "printWidth": 80,
  "semi": true,
  "singleQuote": true,
  "tabWidth": 2,
  "trailingComma": "all",
  "endOfLine": "auto"
}

To format the whole project, or just check without changing files:

npx prettier --write .
npx prettier --check .

Prettier ignores whatever is in .gitignore. If you need to ignore more, list it in a .prettierignore file.

2. ESLint

ESLint has a wizard that creates the config for you:

npm init @eslint/config@latest

Answer the questions (JavaScript or TypeScript, React or not, and so on) and it creates an eslint.config.mjs file. To check the project:

npx eslint .

So ESLint and Prettier don’t fight over formatting, install eslint-config-prettier:

npm install --save-dev eslint-config-prettier

And add it last in your eslint.config.mjs:

import eslintConfigPrettier from 'eslint-config-prettier';

export default [
  // ...your current config
  eslintConfigPrettier,
];

Finally, add two scripts to your package.json:

{
  "scripts": {
    "format": "prettier --write . && eslint --fix .",
    "lint": "prettier --check . && eslint ."
  }
}

3. Husky

You need a Git repository that’s already initialized (git init). Install Husky and run its init command:

npm install --save-dev husky
npx husky init

husky init does two things:

  • Creates the .husky/ folder with a pre-commit hook.
  • Adds the "prepare": "husky" script to your package.json, so hooks install automatically when someone runs npm install.

Now edit .husky/pre-commit so it runs your lint script. The file should contain just this line:

npm run lint

From now on, if the code doesn’t pass Prettier or ESLint, the commit is cancelled.

On a large project, checking everything on every commit can be slow. With lint-staged you can check only the files you’re committing.

4. commitlint and Conventional Commits

Install commitlint with the Conventional Commits config:

npm install --save-dev @commitlint/cli @commitlint/config-conventional

Create commitlint.config.mjs at the root:

export default { extends: ['@commitlint/config-conventional'] };

And create the hook that checks every commit message, in .husky/commit-msg:

npx --no -- commitlint --edit $1

Try it

Try a commit with any message:

git commit -m "changes"

commitlint rejects it because it doesn’t follow the format. This one passes:

git commit -m "feat: add contact form"

The most common types are feat (new feature), fix (bug fix), docs, refactor, test and chore.

Summary

ToolWhat it doesWhere it’s configured
PrettierFormats code.prettierrc
ESLintCatches errorseslint.config.mjs
HuskyRuns commands on every commit.husky/pre-commit, .husky/commit-msg
commitlintChecks the commit messagecommitlint.config.mjs

If you also want a clean history before a pull request, see how to squash commits in Git.