Configuring Hooks
Module: Hooks | Lesson: 3 of 4 | Time: ~10 minutes
What You Will Learn
- How to define hooks in settings.json
- How matchers filter which events trigger a hook
- How to specify commands that hooks execute
Prerequisites
- Completed Lifecycle Events
Where Hooks Are Configured
Hooks live in your Claude Code settings file, inside a hooks section. You can configure them at two levels:
| Scope | File Location |
|---|---|
| Project-level | .claude/settings.json in your project root |
| Global | ~/.claude/settings.json in your home directory |
Project-level hooks only apply when you run Claude Code in that project. Global hooks apply everywhere.
The Hook Configuration Structure
Here is the basic structure:
{
"hooks": {
"EventName": [
{
"matcher": "ToolName",
"command": "your-shell-command-here"
}
]
}
}
Let us break this down piece by piece:
Event
The top-level key is the event name — one of the lifecycle events from the previous lesson (PreToolUse, PostToolUse, SessionStart, etc.):
{
"hooks": {
"PostToolUse": [ ... ]
}
}
Each event contains an array of hook objects. You can have multiple hooks for the same event.
Matcher
The matcher filters which tool triggers the hook. This is only relevant for tool-related events (PreToolUse and PostToolUse). For other events like SessionStart, you do not need a matcher.
{
"matcher": "Edit"
}
Common matcher values:
| Matcher | What It Matches |
|---|---|
"Edit" | When Claude uses the Edit tool |
"Write" | When Claude uses the Write tool |
"Bash" | When Claude runs a terminal command |
"Read" | When Claude reads a file |
You can also use a pattern to match multiple tools or match based on the tool's input:
{
"matcher": "Edit|Write"
}
This matches both Edit and Write tool calls.
If you omit the matcher entirely, the hook fires for every tool call of that event type. Be careful with this — a PostToolUse hook without a matcher runs after every single tool use.
Command
The command is the shell command that runs when the hook fires:
{
"command": "npx prettier --write $CLAUDE_FILE_PATH"
}
The command runs in your system's shell (PowerShell on Windows, bash on Linux/macOS). It has access to environment variables that Claude Code sets based on the context.
Available environment variables:
| Variable | Available In | Contains |
|---|---|---|
$CLAUDE_FILE_PATH | PostToolUse (Edit/Write) | Path of the file that was edited |
$CLAUDE_TOOL_NAME | PreToolUse, PostToolUse | Name of the tool being used |
$CLAUDE_SESSION_ID | All events | The current session ID |
Complete Configuration Examples
Example 1: Auto-Format After Edits
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit",
"command": "npx prettier --write $CLAUDE_FILE_PATH"
}
]
}
}
This runs Prettier on any file Claude edits, ensuring consistent formatting.
Example 2: Block Edits to Protected Files
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit",
"command": "echo $CLAUDE_FILE_PATH | grep -q 'production\\|.env' && exit 1 || exit 0"
}
]
}
}
This blocks Claude from editing any file with "production" or ".env" in the path. The exit 1 tells Claude Code to block the action.
The command above uses grep, which is available in WSL and Git Bash but not in the standard Windows Command Prompt. If you use Windows natively, you may need to write a PowerShell equivalent or use a batch script. See the next lesson for Windows-friendly examples.
Example 3: Session Startup
{
"hooks": {
"SessionStart": [
{
"command": "echo Welcome to the project! Current branch: $(git branch --show-current)"
}
]
}
}
Notice there is no matcher for SessionStart — it fires once when the session begins, and there is no tool to match against.
Example 4: Multiple Hooks on One Event
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit",
"command": "npx prettier --write $CLAUDE_FILE_PATH"
},
{
"matcher": "Bash",
"command": "echo 'Command was executed' >> .claude/audit.log"
}
]
}
}
You can have as many hooks as you want on any event. They run in the order they are listed.
Hook Types by Exit Code
The exit code of your hook command determines what happens:
| Exit Code | Meaning |
|---|---|
0 (success) | The hook ran successfully, Claude continues normally |
| Non-zero (failure) | PreToolUse: the tool call is blocked. PostToolUse: Claude is informed the hook failed |
This is what makes PreToolUse hooks powerful as gatekeepers — returning a non-zero exit code prevents the action from happening.
Try It Yourself
Create your first hook — a simple PostToolUse hook that logs edits:
- Open or create
.claude/settings.jsonin your project:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit",
"command": "echo Edit detected on: $CLAUDE_FILE_PATH"
}
]
}
}
- Start Claude Code in the project:
claude - Ask Claude to make a small change to any file
- Watch for the "Edit detected" message after Claude edits the file
If you do not see the message, double-check your JSON syntax. A missing comma or bracket is the most common configuration mistake. Use a JSON validator to verify.
What You Learned
- Hooks are configured in the
hookssection ofsettings.json - Each hook has an event, an optional matcher, and a command
- Matchers filter which tool triggers the hook (Edit, Write, Bash, etc.)
- Commands are shell commands that can use environment variables like
$CLAUDE_FILE_PATH - Exit codes matter — a non-zero exit from a PreToolUse hook blocks the action
- You can have multiple hooks on the same event — they run in order
How was this lesson? Take 2 minutes to share your feedback — it helps us make the tutorials better for everyone.