Skip to main content

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

Where Hooks Are Configured

Hooks live in your Claude Code settings file, inside a hooks section. You can configure them at two levels:

ScopeFile 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:

MatcherWhat 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.

note

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:

VariableAvailable InContains
$CLAUDE_FILE_PATHPostToolUse (Edit/Write)Path of the file that was edited
$CLAUDE_TOOL_NAMEPreToolUse, PostToolUseName of the tool being used
$CLAUDE_SESSION_IDAll eventsThe 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.

Windows Shell Differences

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 CodeMeaning
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:

  1. Open or create .claude/settings.json in your project:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit",
"command": "echo Edit detected on: $CLAUDE_FILE_PATH"
}
]
}
}
  1. Start Claude Code in the project: claude
  2. Ask Claude to make a small change to any file
  3. Watch for the "Edit detected" message after Claude edits the file
tip

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 hooks section of settings.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

Help Us Improve

How was this lesson? Take 2 minutes to share your feedback — it helps us make the tutorials better for everyone.

Give Feedback →

Next Up

Next: Practical Hook Examples