Hooks Generator
Generate a PreToolUse / PostToolUse hook without the guesswork.
- $35 Free
- 45 sec
- No signup
Pick the hook event and matcher
Enter the command to run
Copy the hooks block into settings.json
You get: A valid hooks block for settings.json wired to your event.
Configure the hook
settings.json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.command' | grep -qv 'rm -rf' || exit 2"
}
]
}
]
}
}Merge this into your .claude/settings.json (project) or ~/.claude/settings.json (personal). A PreToolUse hook can block an action by exiting with code 2.
What Claude Code hooks actually are
A hook is a shell command that Claude Code runs automatically at a specific point in its lifecycle. You register hooks in settings.json under a `hooks` key, grouped by event name. When that event fires - a tool is about to run, a response just finished, you submitted a prompt - Claude Code executes your command, passes it context as JSON on standard input, and reads its exit code and output. That is the entire model: an event, a matcher, and a command. Hooks are how you turn Claude Code from a thing that suggests actions into a thing that enforces your rules deterministically, because a hook runs every single time, not just when the model remembers to.
The generator above builds a valid hooks block for you. Pick the event you want to react to, pick which tools should trigger it (for tool-lifecycle events), and paste the command you want run. The output is drop-in JSON you merge into settings.json - no guessing at the schema, no malformed matcher strings.
The hook events and when they fire
- PreToolUse - fires before a tool call runs. This is the one that can block. Exit with code 2 and Claude Code cancels the tool call and feeds your message back to the model. Use it for guardrails: block `rm -rf`, block writes outside a directory, require a lint pass before an edit.
- PostToolUse - fires after a tool call completes. Great for reactions: auto-format a file after every Edit, run a type check after a Write, log what was touched.
- UserPromptSubmit - fires when you submit a prompt, before the model sees it. You can inject extra context or validate the prompt.
- Stop - fires when Claude finishes its main response. Good for a done chime, a summary log, or kicking off a follow-up job.
- SubagentStop - fires when a spawned subagent finishes its work.
- SessionStart - fires when a session begins. Use it to print environment info, warm a cache, or load state.
- Notification - fires when Claude Code sends you a notification (for example, when it needs input).
- PreCompact - fires before Claude Code compacts the conversation to reclaim context.
PreToolUse exit code 2 is the block signal
A PreToolUse hook that exits with code 2 stops the tool call from happening and returns your stderr text to the model. Exit 0 lets it proceed. That single mechanic is how you build hard, deterministic guardrails that the model cannot talk itself out of.
How the matcher works
For the tool-lifecycle events (PreToolUse and PostToolUse), the matcher decides which tools trigger your hook. It is matched against the tool name. An empty matcher or a broad pattern catches every tool; a specific name like Bash catches only shell commands; an alternation like Edit|Write|MultiEdit catches any of several tools. The other events do not run against a specific tool, so they take no matcher - they simply fire on the event itself. The generator hides the matcher field for those events so you never write an ignored one.
What your command receives
Claude Code passes hook context to your command as a JSON object on standard input. That payload includes information about the event - for tool events, the tool name and its input arguments; for prompt events, the submitted text. A common pattern is to pipe stdin through a JSON tool like jq to pull out the field you care about, then decide whether to allow, block, or transform. Your command's exit code controls flow (0 to proceed, 2 to block on PreToolUse), and text you print can be surfaced back to the model or the user.
Project hooks vs personal hooks
Where you put the block matters. Hooks in a project's .claude/settings.json travel with the repo and apply to anyone working in it - use this for team guardrails like formatting and lint gates. Hooks in your personal ~/.claude/settings.json apply to you across every project - use this for your own preferences like a done chime or a personal logging habit. If you want a project hook that stays out of version control, .claude/settings.local.json is the local, gitignored variant. Because hooks execute real shell commands on your machine, only add hooks you understand and trust.
Practical hooks worth stealing
- Auto-format on save: PostToolUse matching Edit|Write that runs your formatter on the changed file, so Claude's edits are always clean.
- Block dangerous shell: PreToolUse matching Bash that inspects the command and exits 2 if it contains a destructive pattern.
- Type-check gate: PostToolUse matching Edit|Write that runs your type checker and reports failures back so Claude fixes them itself.
- Session banner: SessionStart that prints the current branch, environment, and any active feature flags.
- Done chime: Stop that plays a sound or sends a desktop notification so you know a long task finished.
- Prompt logging: UserPromptSubmit that appends your prompts to a local file for later review of what you actually asked.
Debugging a hook that will not fire
If a hook does not seem to run, check the obvious things first: the JSON is valid and lives under the right event key, the matcher actually matches the tool you expect, and the command is executable and on your PATH. Test the command in isolation by feeding it a sample JSON payload on stdin. Remember that a PreToolUse hook that always exits 2 will block everything it matches, which looks like Claude Code refusing to work - loosen the matcher or fix the condition. When in doubt, start with a trivial hook that just echoes and confirm it triggers before wiring in real logic.
Frequently asked questions
Where do hooks live?
In a settings.json file under the `hooks` key. Project-wide hooks go in .claude/settings.json in your repo; personal hooks go in ~/.claude/settings.json. Use .claude/settings.local.json for project hooks you do not want committed.
How does a PreToolUse hook block an action?
It exits with code 2. Claude Code cancels the pending tool call and feeds your stderr text back to the model. Exit 0 lets the tool run normally.
What data does my hook get?
A JSON payload on standard input describing the event - for tool events that includes the tool name and its input. Pipe it through a JSON parser to read the fields you need.
Which events use a matcher?
The tool-lifecycle events, PreToolUse and PostToolUse, match against the tool name. Events like Stop, SessionStart, and UserPromptSubmit fire on the event itself and take no matcher.
Can I run more than one hook on the same event?
Yes. Each event key holds an array, and each matcher entry holds an array of hooks, so you can register several commands to run in sequence.
Are hooks safe?
They run real shell commands on your machine with your permissions, so treat them like any script you install. Only add hooks you understand, and be careful with project hooks pulled from repos you did not write.
Liked this tool? The club is the next step.
Join Claude Code Club for $9/month. 650+ lessons, weekly updates, and the workflows behind every tool on this site.
- No experience needed
- Cancel anytime
- Updated weekly
