Automatically formats source code files when Claude Code edits them.
| Language | Extensions | Formatter | Command |
|---|---|---|---|
| JavaScript/TypeScript | .js, .jsx, .ts, .tsx |
biome (prettier fallback) | biome format --write or prettier --write |
| Python | .py |
ruff | uv tool run ruff format |
| Markdown | .md |
prettier | prettier --write |
| Go | .go |
goimports + go fmt | goimports -w then go fmt |
| Kotlin | .kt, .kts |
ktlint (ktfmt fallback) | ktlint --format or ktfmt |
Notes:
- For JavaScript/TypeScript files, the hook will try biome first. If biome is not installed, it will automatically fall back to prettier.
- For Kotlin files, the hook will try ktlint first, then fall back to ktfmt if ktlint is not available.
Clone this repository or download the shell script:
# Option A: Clone the repository
git clone https://github.com/YOUR_USERNAME/claude-format-hook.git ~/claude-format-hook
# Option B: Just download the script
curl -o ~/format-code.sh https://raw.githubusercontent.com/YOUR_USERNAME/claude-format-hook/main/format-code.sh
chmod +x ~/format-code.shInstall the formatters for the languages you use:
# JavaScript/TypeScript - Biome
npm install -g @biomejs/biome
# or
brew install biome
# Python - uv and ruff
curl -LsSf https://astral.sh/uv/install.sh | sh
uv tool install ruff
# Markdown - Prettier
npm install -g prettier
# Go - goimports
go install golang.org/x/tools/cmd/goimports@latest
# Kotlin - ktlint
brew install ktlint
# or download from: https://github.com/pinterest/ktlint/releases
# Kotlin - ktfmt (alternative)
# Download JAR from: https://github.com/facebook/ktfmt/releases
# Then create alias: alias ktfmt='java -jar /path/to/ktfmt.jar'Copy the settings file to Claude's configuration directory:
# Create Claude config directory if it doesn't exist
mkdir -p ~/.claude
# Option A: If you cloned the repository
cp ~/claude-format-hook/settings.json ~/.claude/settings.json
# Option B: If you only downloaded the script, create the settings file
cat > ~/.claude/settings.json << 'EOF'
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|MultiEdit|Write",
"hooks": [
{
"type": "command",
"command": "~/format-code.sh"
}
]
}
]
}
}
EOFNote: If you already have a ~/.claude/settings.json file, you'll need to merge the hooks configuration into your existing file.
The hook is now active! To test it:
- Ask Claude to create or edit a source file
- The file will be automatically formatted after Claude saves it
- The formatting happens silently in the background
- When Claude uses the Edit, MultiEdit, or Write tools, the hook is triggered
- Claude sends information about the edited file to
format-code.shas JSON via stdin - The script extracts the file path and checks its extension
- If a supported formatter is installed, it runs automatically
- If a formatter isn't available, the script exits silently without errors
- Non-blocking: Won't interrupt Claude's workflow if formatting fails
- Silent operation: Formatters run in the background without output
- Graceful degradation: Missing formatters are skipped automatically
- Multiple formatter support: Handles different languages with their preferred tools
- Check that
~/.claude/settings.jsonexists and contains the hook configuration - Ensure the script is executable:
chmod +x ~/format-code.sh(or wherever you saved it) - Verify the path in settings.json matches where you saved the script
- Check if the formatter is installed:
which biome(or other formatter) - Run the formatter manually to test:
biome format --write yourfile.js - Check the file extension is supported (see table above)
You can test the script with sample JSON:
echo '{"tool_input": {"file_path": "/path/to/test.py"}}' | ./format-code.shTo add support for additional file types, edit format-code.sh and add a new case in the switch statement:
case "$extension" in
# ... existing cases ...
# Add your custom extension
rs)
if command -v rustfmt &> /dev/null; then
rustfmt "$file_path" &> /dev/null
fi
;;
esacformat-code.sh- The main formatting scriptsettings.json- Claude Code hook configurationREADME.md- This documentation