Prettier 15 ๐จ Pre-commit Hooks โ Husky & lint-staged integration
A pre-commit hook is the last line of defense before unformatted code enters the repository. The editor integration formats on save, and the CI check catches what the editor missed, but both depend on the developer’s environment and the pipeline’s timing. The pre-commit hook runs on the developer’s machine, against the files that are actually staged, before the commit object is created. If the hook fails, the commit does not happen. The combination of Husky (the Git hook manager) and lint-staged (the staged-file runner) is the standard implementation: Husky installs the hook, lint-staged selects the staged files, and Prettier formats them.
This chapter covers the installation and configuration of the Husky and lint-staged pair, the prettier --ignore-unknown flag that prevents the hook from failing on binary files, and the prepare script that makes the hook active for every contributor who clones the repository.
Key point: The pre-commit hook formats the staged files with prettier --write and re-stages them. The commit proceeds with the formatted content, and the developer does not need to remember to run Prettier manually. The --ignore-unknown flag is required because lint-staged passes every staged file to the command, including images and other binaries that Prettier cannot parse.
Why the pre-commit hook exists
The editor bypass problem. A developer can disable format-on-save, or use an editor that is not configured with Prettier, or edit a file through a tool that does not trigger the editor’s save hook. The pre-commit hook runs regardless of the editor. It is the one formatting gate that the developer’s local configuration cannot accidentally disable.
The CI latency problem. The CI check catches unformatted code, but only after the push and the pipeline run. The developer has to wait for the failure, fix the formatting, and push again. The pre-commit hook catches the same problem before the commit, which means the fix is immediate and the CI check passes on the first try.
The staged-file problem. Running prettier --write . before every commit would format the entire repository, including files that are not part of the commit. The formatting changes on the unrelated files would be staged or left in the working tree, and the developer would have to decide what to do with them. lint-staged solves this by passing only the staged files to the command, which keeps the formatting scoped to the commit.
a. The installation and the Husky initialization
The installation adds three packages as dev dependencies: husky, lint-staged, and prettier (if it is not already installed).
npm install --save-dev husky lint-staged prettier
The npx husky init command creates the .husky/ directory and adds a prepare script to package.json .
npx husky init
The prepare script is what makes the hook active for every contributor. When a new developer clones the repository and runs npm install, the prepare script runs automatically, and Husky installs the hooks into the .git/hooks/ directory. The developer does not need to run husky init manually .
The npx husky init command also creates a sample .husky/pre-commit file. The file is replaced with the lint-staged command.
# .husky/pre-commit
npx lint-staged
The hook file does not need a shebang in Husky v9 and later. The file is a POSIX shell script, and the npx lint-staged line is the entire content .
b. The lint-staged configuration
The lint-staged configuration is the mapping from file patterns to commands. The configuration can live in package.json or in a dedicated .lintstagedrc file. The simplest configuration passes every staged file to Prettier with the --write flag and the --ignore-unknown flag.
{
"*": "prettier --ignore-unknown --write"
}
The * pattern matches every staged file. The prettier --ignore-unknown --write command formats the files that Prettier can parse and skips the files that it cannot. The --ignore-unknown flag is the mechanism that prevents the hook from failing when a .png or a .woff2 file is staged .
A more granular configuration uses file patterns to run different commands on different file types. A project that uses ESLint alongside Prettier runs eslint --fix first, then prettier --write, so that the ESLint fixes are also formatted .
{
"*.{js,ts,jsx,tsx}": ["eslint --fix", "prettier --write"],
"*.{css,scss,json,md}": "prettier --write"
}
The order matters. ESLint fixes the code first, and Prettier formats the result. If the order were reversed, the ESLint fixes would not be formatted .
c. The verification and the failure behavior
The hook is verified by staging a file with a formatting violation and running npx lint-staged directly. The command formats the file, re-stages it, and exits zero. The next git commit includes the formatted content.
# Stage a file with a formatting violation
git add src/app.js
npx lint-staged
If Prettier fails to parse a staged file, the hook exits non-zero, and the commit is blocked. The --ignore-unknown flag prevents this for the file types that Prettier does not support, but a syntax error in a file that Prettier does support still blocks the commit. The developer fixes the syntax error and retries the commit.
The hook can be bypassed with git commit --no-verify, which is the escape hatch for the cases where the developer needs to commit a work-in-progress that does not pass the hook. The HUSKY=0 environment variable disables all hooks temporarily .
Complete Example Session
# ============================================
# PART 1: INSTALL THE DEPENDENCIES
# ============================================
npm install --save-dev husky lint-staged prettier
# ============================================
# PART 2: INITIALIZE HUSKY
# ============================================
npx husky init
# Creates .husky/ and adds "prepare": "husky" to package.json.
# ============================================
# PART 3: CREATE THE PRE-COMMIT HOOK
# ============================================
# .husky/pre-commit
npx lint-staged
// ============================================
// PART 4: THE LINT-STAGED CONFIG
// ============================================
{
"lint-staged": {
"*": "prettier --ignore-unknown --write"
}
}
# ============================================
# PART 5: THE VERIFICATION
# ============================================
git add src/app.js
npx lint-staged
# The file is formatted and re-staged.
# ============================================
# PART 6: THE COMMIT
# ============================================
git commit -m "feat: add feature"
# The hook runs lint-staged.
# The formatted file is committed.
// ============================================
// PART 7: THE GRANULAR CONFIG
// ============================================
{
"lint-staged": {
"*.{js,ts,jsx,tsx}": ["eslint --fix", "prettier --write"],
"*.{css,scss,json,md}": "prettier --write"
}
}
# ============================================
# PART 8: THE PREPARE SCRIPT
# ============================================
# package.json
"prepare": "husky"
# A new clone runs npm install, and the hooks are installed.
# ============================================
# PART 9: THE BYPASS
# ============================================
git commit -m "wip" --no-verify
# or
HUSKY=0 git commit -m "wip"
# ============================================
# PART 10: THE DECISION
# ============================================
# All files โ "*": "prettier --ignore-unknown --write"
# With ESLint โ eslint --fix first, then prettier --write
The ten parts covered the installation, the Husky initialization, the pre-commit hook, the lint-staged config, the verification, the commit, the granular config, the prepare script, the bypass, and the decision.
Quick Reference
The packages
| Package | Purpose |
|---|---|
husky | Installs the Git hooks |
lint-staged | Runs commands on staged files |
prettier | The formatter |
The setup commands
| Command | Effect |
|---|---|
npm install --save-dev husky lint-staged prettier | Install dependencies |
npx husky init | Create .husky/ and the prepare script |
npx lint-staged | Run the hook manually |
The configuration
| File | Content |
|---|---|
.husky/pre-commit | npx lint-staged |
package.json | "prepare": "husky" |
package.json or .lintstagedrc | The lint-staged mapping |
The flags
| Flag | Purpose |
|---|---|
--write | Format the file in place |
--ignore-unknown | Skip files Prettier cannot parse |
--no-verify | Bypass the hook for one commit |
HUSKY=0 | Disable all hooks temporarily |
Best Practices
โ Do This:
// Use --ignore-unknown in the lint-staged command
"*": "prettier --ignore-unknown --write" // โ
# Verify the hook manually before committing
npx lint-staged // โ
// Run ESLint before Prettier
"*.{js,ts}": ["eslint --fix", "prettier --write"] // โ
# Keep the prepare script so new clones install the hook
"prepare": "husky" // โ
โ Don’t Do This:
// Omit --ignore-unknown
"*": "prettier --write" // โ (fails on images)
# Run prettier on the whole repo in the hook
"*": "prettier --write ." // โ (formats unstaged files)
// Run Prettier before ESLint
"*.{js,ts}": ["prettier --write", "eslint --fix"] // โ (ESLint fixes unformatted)
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
Hook fails on a .png file | --ignore-unknown not passed | Add the flag |
| Hook does not run for a new clone | The prepare script is missing | Add "prepare": "husky" |
| Unstaged files are formatted | The command targets . instead of the staged files | Use the lint-staged * pattern |
| ESLint fixes are unformatted | Prettier runs before ESLint | Reverse the order |
| Hook blocks a WIP commit | Formatting violation | Fix it, or use --no-verify |
Real-World Examples
1. The install
npm install --save-dev husky lint-staged prettier
2. The init
npx husky init
3. The hook
# .husky/pre-commit
npx lint-staged
4. The config
{ "lint-staged": { "*": "prettier --ignore-unknown --write" } }
5. The prepare script
{ "prepare": "husky" }
6. The ESLint + Prettier config
{ "lint-staged": { "*.{js,ts}": ["eslint --fix", "prettier --write"] } }
7. The manual verification
npx lint-staged
8. The bypass
git commit --no-verify
9. The disable
HUSKY=0 git commit -m "wip"
10. The decision
All files โ "*": "prettier --ignore-unknown --write"
Visual
The pre-commit flow
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ git commit โ
โ โ โ
โ โผ โ
โ .husky/pre-commit runs โ
โ โ โ
โ โผ โ
โ npx lint-staged โ
โ โ โ
โ โผ โ
โ Selects staged files โ
โ โ โ
โ โผ โ
โ prettier --ignore-unknown --write โ
โ โ โ
โ โผ โ
โ Re-stages the formatted files โ
โ โ โ
โ โผ โ
โ Commit proceeds with formatted content โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
The two flags
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ --write โ
โ Formats the file in place. โ
โ โ
โ --ignore-unknown โ
โ Skips files Prettier cannot parse (images, etc.). โ
โ โ
โ Both are required in the lint-staged command. โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
The prepare script
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ New clone โ
โ โ โ
โ โผ โ
โ npm install โ
โ โ โ
โ โผ โ
โ "prepare": "husky" runs โ
โ โ โ
โ โผ โ
โ Hooks installed in .git/hooks/ โ
โ โ โ
โ โผ โ
โ The pre-commit hook is active โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
The ESLint and Prettier order
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ eslint --fix โ fixes the code โ
โ โ โ
โ โผ โ
โ prettier --write โ formats the fixed code โ
โ โ
โ The order matters. โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Summary
| Item | Value |
|---|---|
| Hook manager | husky |
| Staged-file runner | lint-staged |
| Init command | npx husky init |
| Hook file | .husky/pre-commit |
| Hook content | npx lint-staged |
| Config location | package.json or .lintstagedrc |
| The command | prettier --ignore-unknown --write |
| The prepare script | "prepare": "husky" |
| Bypass | --no-verify or HUSKY=0 |
Key takeaways:
- The pre-commit hook is the formatting gate that the developer’s editor cannot bypass. It runs on the staged files, formats them with Prettier, and re-stages the result before the commit object is created. If the formatting fails, the commit is blocked.
- Husky installs the hook, and lint-staged selects the files. The
npx husky initcommand creates the.husky/directory and thepreparescript. The.husky/pre-commitfile runsnpx lint-staged, and the lint-staged configuration maps the file patterns to the Prettier command . - The
--ignore-unknownflag is required. lint-staged passes every staged file to the command, including images and other binaries. Without the flag, Prettier fails on the first file it cannot parse, and the commit is blocked for a reason that has nothing to do with formatting . - The
preparescript makes the hook active for every contributor. When a new developer clones the repository and runsnpm install, thepreparescript runshusky, and the hooks are installed into.git/hooks/. The developer does not need to runhusky initmanually . - The ESLint and Prettier commands run in order. If the project uses both,
eslint --fixruns first, andprettier --writeformats the result. The reverse order would leave the ESLint fixes unformatted . - The hook can be bypassed for a work-in-progress commit.
git commit --no-verifyskips the hook for one commit, andHUSKY=0disables all hooks temporarily. The bypass is an escape hatch, not a default .
Remember: The pre-commit hook is the bridge between the editor and CI. The editor formats on save, the CI check catches the misses, and the pre-commit hook catches the files that were staged without a save. Husky installs the hook, lint-staged scopes it to the staged files, and Prettier formats them. The --ignore-unknown flag keeps the hook from failing on binary files, and the prepare script keeps the hook active for every contributor. The result is a repository where the formatted code is the committed code, and the CI check is a confirmation rather than a discovery.
Stop using slow, ad-bloated tool sites! ๐คฎ
๐ Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
โ
Finance (Mortgage, Interest, Inflation)
โ
Tech (Base64, JSON, Dev Suite, IP)
โ
Health (BMI, BMR, TDEE)
โ
Productivity (Timer, Workspace, QR)
โก๏ธ Fast & Private
๐ No data leaves your device
๐ 100% Free
๐ Use it now: https://tools.kandz.me
๐ Bookmark itโyouโll need it later!