DEV Community

Cover image for Git hooks with Husky
Željko Šević
Željko Šević

Posted on • Originally published at sevic.dev

Git hooks with Husky

Git hooks run scripts around local Git events (pre-commit, commit-msg, pre-push, and others). They are a good place for fast checks - lint, format, and lightweight validation - before bad changes leave your machine. Hooks are not a substitute for CI; treat them as an early filter.

Husky wires those hooks through Git's core.hooksPath. Hook files live in .husky/ as plain shell scripts, and a prepare script installs them after npm install so every clone gets the same setup.

This post covers Husky v9 setup, common hooks, lint-staged, skipping hooks in CI, pitfalls, and a runnable demo that blocks messy commits.

Prerequisites

  • Node.js version 26
  • A Git repository (git init if you are starting fresh)
  • npm i -D husky
  • Optional but recommended: npm i -D lint-staged plus ESLint and Prettier

Setup

Install Husky and initialize it:

npm i -D husky
npx husky init
Enter fullscreen mode Exit fullscreen mode

husky init creates .husky/pre-commit and adds a prepare script to package.json:

{
  "scripts": {
    "prepare": "husky"
  }
}
Enter fullscreen mode Exit fullscreen mode

prepare runs after local npm install, so teammates get hooks without a separate setup step. Replace the default pre-commit body with the checks you want.

Mental model

Piece Role
prepare / husky Points Git at .husky/ via core.hooksPath
.husky/<hook> Shell script Git runs for that event
lint-staged Runs tools only on staged files (typical pre-commit target)
CI Still runs full lint/test; hooks are local only

Husky does not invent a new hook system - it configures native Git hooks and keeps the scripts in the repo.

Pre-commit with lint-staged

Running eslint . or prettier --write on the whole tree from pre-commit gets slow. lint-staged limits work to staged files.

npm i -D lint-staged eslint prettier
Enter fullscreen mode Exit fullscreen mode
// package.json
{
  "lint-staged": {
    "*.{js,ts}": ["eslint --fix", "prettier --write"]
  }
}
Enter fullscreen mode Exit fullscreen mode
# .husky/pre-commit
npx lint-staged
Enter fullscreen mode Exit fullscreen mode

On git commit, Git runs .husky/pre-commitlint-staged → ESLint/Prettier on matching staged paths. Auto-fixed files are restaged. If a check exits non-zero, the commit is aborted.

Keep pre-commit fast and file-local. Put slow or whole-repo work in pre-push or CI.

Other useful hooks

commit-msg - validate the message file Git passes as $1:

# .husky/commit-msg
message=$(cat "$1")

if echo "$message" | grep -qE '^(WIP|wip)($|[^a-zA-Z])'; then
  echo "WIP commits are blocked by the commit-msg hook"
  exit 1
fi
Enter fullscreen mode Exit fullscreen mode

pre-push - run a quicker subset of CI before a push:

# .husky/pre-push
npm test
Enter fullscreen mode Exit fullscreen mode

Create a hook by adding an executable script under .husky/ with the Git hook name. Prefer POSIX shell for Windows compatibility.

Skipping hooks

Skip once with Git's no-verify flag:

git commit -m "emergency fix" -n
Enter fullscreen mode Exit fullscreen mode

Disable Husky for a command or environment:

HUSKY=0 git commit -m "skip hooks"
Enter fullscreen mode Exit fullscreen mode

Set HUSKY=0 (or HUSKY: 0 in CI env) on CI/Docker so installs do not try to configure local hooks. If production installs omit devDependencies, use a safe prepare script so missing husky does not fail the install:

{
  "scripts": {
    "prepare": "husky || true"
  }
}
Enter fullscreen mode Exit fullscreen mode

Pitfalls

  • Hooks are local - they do not run on the remote; CI still matters.
  • --no-verify / HUSKY=0 - anyone can bypass hooks; do not rely on them for security.
  • GUI + Node version managers - GUI clients often lack the shell PATH where nvm/fnm put Node. Source your version manager from ~/.config/husky/init.sh.
  • Nested package vs Git root - Husky will not install into a parent ../ by default. In a monorepo, install Husky at the repo root (or adjust prepare as in the Husky how-to).
  • Slow pre-commit - full test suites belong in pre-push or CI; keep commit hooks on staged files.

When to use what

Approach Good for
Husky + lint-staged Fast lint/format on staged files before commit
Husky commit-msg Lightweight message conventions locally
Husky pre-push Short tests before sharing a branch
CI only Full lint, build, and test on every change

Pair Husky with ESLint and Prettier for the usual Node.js quality gate on commit.

Need help with your project?

Get personalized advice on your architecture, code, or career in a 45-minute 1-on-1 consultation.

Book a consultation

Top comments (0)