auto-prettier — Claude Code Hook for Automatic Prettier Formatting | AI Code Toolkit
Highest install ratePostToolUseWarn on failure

auto-prettier

The reference PostToolUse hook for automatic Prettier formatting. 25 lines of bash, 3 lines of settings.json, and every JS/TS/CSS/HTML/JSON/MD/YAML file Claude writes lands formatted correctly — silently, using your project's own config.

Category
Formatter
Phase
PostToolUse
Matcher
Write|Edit
Latency
~100ms per file
Updated
Aug 2026

auto-prettier is the poster child of the "install-once-forget-forever" hook category. Twenty-five lines of bash, three lines of settings.json, and from that moment on every JavaScript, TypeScript, CSS, HTML, JSON, Markdown, or YAML file Claude Code writes or edits comes out formatted correctly — without you asking, without slowing the interaction down, without leaving any drift for humans to clean up later.

The hook runs as a PostToolUse shell command matching Write|Edit. When Claude writes a matching file, the hook runs prettier --write, which reads your project's existing .prettierrc and formats accordingly. If Prettier succeeded, the operation continues silently. If Prettier couldn't format the file (syntax error, config problem), the error goes back to Claude so it can address it on the next turn — but the write still happens. That warn-vs-block discipline is what makes formatter hooks comfortable to install.

If you're on a JS/TS project and haven't installed this yet, do it before any other hook. It's the single highest install-to-value ratio in the whole hooks library.

The Full Config

The full auto-prettier hook

Two files: the bash script and the settings.json wiring. Both commit to git so every teammate gets the hook automatically.

bash .claude/hooks/auto-prettier.sh
#!/bin/bash # Runs prettier --write on files Claude wrote or edited. # Silent on success; passes formatter errors back to Claude on failure. input=$(cat) file_path=$(echo "$input" | jq -r '.tool_input.file_path // .tool_input.path // ""') # Skip if no file, or file extension isn't a prettier target if [ -z "$file_path" ] || \ ! [[ "$file_path" =~ \.(js|jsx|ts|tsx|mjs|cjs|css|scss|less|html|json|md|mdx|yaml|yml)$ ]]; then exit 0 fi # Skip generated and dependency directories if [[ "$file_path" =~ (node_modules|\.next|dist|build|__generated__|coverage)/ ]]; then exit 0 fi # Run prettier with team config; capture errors output=$(npx --no-install prettier --write "$file_path" 2>&1) result=$? if [ $result -ne 0 ]; then # Prettier not installed — don't punish the interaction if echo "$output" | grep -q "could not determine executable"; then echo "prettier not installed; skipping format" >&2 exit 0 fi # Real error — warn Claude but don't block the write echo "Prettier failed on $file_path:" echo "$output" exit 1 fi exit 0 # Silent success

And the settings.json wiring:

json .claude/settings.json (excerpt)
{ "hooks": { "PostToolUse": [ { "matcher": "Write|Edit", "hooks": [ { "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/auto-prettier.sh" } ] } ] } }
Install

How to install

Four steps — save the script, make it executable, register in settings.json, restart Claude Code. Under five minutes end to end.

1. Save the script Step 1

Save the bash script to .claude/hooks/auto-prettier.sh at your repo root. Create the directory if it doesn't exist. Commit both to git so teammates get the hook on next pull.

2. Make executable Step 2

Run chmod +x .claude/hooks/auto-prettier.sh. Claude Code executes the file directly, so non-executable scripts silently fail. This is the single most common install mistake.

3. Register in settings.json Step 3

Add the JSON snippet above to .claude/settings.json. Use $CLAUDE_PROJECT_DIR in the path so the hook works for teammates whose repo lives at a different absolute path.

4. Verify Step 4

Restart Claude Code. Ask it to write any TypeScript file, then check whether it comes out formatted. If not, check chmod, matcher pattern (case-sensitive: Write|Edit), and file path in settings.json.

Usage

How to use it

There's no manual invocation — the hook fires automatically. These are the interactions where you'll notice it working.

write a new React component at components/user-card.tsx
The most common case. Claude writes the file, the hook runs Prettier on it, the file lands formatted. Total added latency: ~100ms. You never see the hook run.
fix the type errors in this component
The Edit path. Claude edits the file, the hook runs Prettier on the edited file. Same behavior, same silent success.
add a README section explaining the auth flow
Works on non-code files too. Markdown, JSON, YAML, and CSS are all Prettier targets. Your READMEs come out consistently formatted.
convert the config from JavaScript to TypeScript
If Claude renames config.js to config.ts and rewrites content, the hook fires on the write and formats the new file. Renames+edits work transparently.
any Claude interaction on a project with a .prettierignore
The hook invokes Prettier's own CLI, which reads .prettierignore natively. Files in your ignore list get skipped automatically. No extra config in the hook needed.
Variations

Alternative configs

Adjust the hook for specific project needs.

Prettier + ESLint --fix combined
For teams that want both formatting and lint auto-fix in one hook
Add after the Prettier block: "Run npx --no-install eslint --fix "$file_path". Same warn-on-failure discipline. Runs after Prettier so ESLint's auto-fixes don't get reformatted away." Or install the separate auto-eslint hook for clean separation.
Prettier + typecheck
For projects where you want type checking after every write
Add a second PostToolUse hook that runs tsc --noEmit scoped to the affected package. Warns on type errors so Claude can fix them next turn. See auto-typecheck for the full setup.
Monorepo-aware variant
If Prettier config differs per workspace
Change the npx prettier call to cd "$(dirname "$file_path")" && npx --no-install prettier --write "$file_path". This runs Prettier from the closest config — important in Turborepo/Nx setups where each package has its own Prettier config.
Troubleshooting

Common issues and fixes

The five issues teams hit most often installing this hook.

Hook silently doesn't run
Script isn't executable, or the matcher pattern doesn't match Claude's tool name.
Run chmod +x .claude/hooks/auto-prettier.sh. Confirm the matcher in settings.json is exactly Write|Edit (case-sensitive, no spaces). Test by asking Claude to write any TS file and checking whether the file lands formatted.
Hook runs but doesn't format the file
Prettier isn't installed in the project (no prettier in node_modules), or the file extension isn't in the matched list.
Confirm pnpm add -D prettier (or npm install). Check the file extension is in the script's regex — add it if needed (e.g., vue, svelte). The --no-install flag in the script prevents npx from silently installing the wrong version.
Hook runs too slowly — interactions feel laggy
Formatting large files, or formatting files from generated directories that weren't excluded.
Confirm the generated-directory skip list in the script matches your project layout (add .turbo, storybook-static, etc. as needed). If large files are legitimately slow, consider Prettier's cache: npx prettier --write --cache speeds up repeat runs significantly.
Errors from Prettier keep coming back to Claude but don't get fixed
Prettier is reporting config or syntax errors that Claude interprets as safe to ignore.
Make the error message more actionable. Change the fail branch to include: "Prettier reported errors. Address them before continuing." Alternatively, upgrade to exit 2 (block) if you want strict enforcement — but this is aggressive; most teams stick with warn.
Hook interferes with your team's git pre-commit formatter
Both are running; one produces slightly different output than the other. Common with Prettier version skew.
Pin exact Prettier versions in package.json ("prettier": "3.4.2", not ^3). Both the hook and git pre-commit will use the same binary. Zero drift, zero cleanup PRs.

🐛 Hit an error?

AI Error Hub covers Claude Code errors and stack traces — cross-referenced with everything on this site.

Visit AI Error Hub →
FAQ

Frequently asked questions

The questions developers ask most about auto-prettier.

Blocking on formatter failure means every syntax error becomes a wall Claude can't get past on the current turn. Warning lets Claude see the error message and fix it on the next turn — the actual desired behavior.

Reserve block-level severity (exit 2) for security hooks and destructive-operation gates. For formatters, warn (exit 1) is right.

Not meaningfully. Prettier on a single file runs in about 100ms.

For a session with 30 file edits that's 3 seconds of total added latency across the whole session — imperceptible in interactive use.

The exception is very large generated files, which is why the script excludes node_modules, dist, build, and other generated directories.

The hook exits 0 silently with a stderr note. That's intentional — you don't want the hook to punish an interaction because a teammate hasn't run pnpm install yet, or because Prettier isn't a dev dependency in that repo.

The "prettier not installed; skipping format" log leaves a paper trail so someone eventually adds it.

Yes — the hook invokes Prettier's own CLI, which reads .prettierignore natively. No extra config in the hook needed.

The hook's own skip list (node_modules, .next, dist, etc.) is a defense-in-depth optimization that avoids even shelling out for known-ignored paths — faster than letting Prettier read and then ignore them.

Yes, and it's common on JS/TS projects. auto-prettier + auto-eslint + auto-typecheck all run on TypeScript writes.

They execute in the order declared in settings.json. Just watch total latency — three formatters running on every write can add up.

In monorepos, keep the typecheck one scoped to the affected package to avoid the slowest hook running project-wide.

No. Hooks fire on tool-use events regardless of who invoked the tool. When a subagent writes a file, the PostToolUse auto-prettier hook runs just as it would in the main conversation.

This is important — team-shared formatters must apply uniformly. If nextjs-15-expert or postgres-dba writes a file, it still gets formatted.

Complementary layers:

  • Editor plugin — formats when a human saves in the editor.
  • auto-prettier hook — formats when Claude Code writes via its tools.
  • Git pre-commit — catches anything reaching commit stage from either source.

Best practice: all three. Layers, not silver bullets.

Adapt the script — swap npx prettier --write for biome format --write or dprint fmt. The rest of the script (path filtering, exit-code discipline, generated-dir exclusion) works identically.

If you'd rather not maintain the script yourself, the Repo Analyzer tool detects Biome/dprint and generates a matching hook.

Yes, absolutely. Commit .claude/settings.json and .claude/hooks/ together — every teammate gets the same setup on next pull.

This is exactly the pattern that makes Claude Code output indistinguishable from human output. No drift, no version-skew debates, no cleanup PRs.

Yes — the hook invokes your project's Prettier install, which reads .prettierrc and loads any plugins listed there.

If you have @prettier/plugin-tailwindcss or prettier-plugin-organize-imports configured, they apply on every Claude write automatically. No changes to the hook needed.

Share with