Cursor Hooks Library
Cursor hooks are small shell scripts that fire at fixed points in the agent loop — before a prompt is sent, before a shell command runs, after a file is edited, or when the agent stops. Use them to enforce quality, catch SEO regressions, block surprise installs, and keep costs in check. Every hook below is copy-ready.
The four lifecycle events
beforeSubmitPromptRuns before your prompt is sent — inspect or warn on the request.beforeShellExecutionRuns before a shell command — allow or deny it.afterFileEditRuns after the agent edits a file — validate the result.stopRuns when the agent finishes — summarize, notify, or follow up.Hooks run real commands
Filter by event
Filter by category
Showing 13 of 13 hooks
Block surprise package installs
beforeShellExecutionDeny package install commands and ask the agent to justify the dependency before proceeding.
When to use it
Prevent Cursor from adding dependencies without approval, especially in web apps and automation projects.
Notes
Good for keeping projects lean and preventing unnecessary dependency bloat.
Save as .cursor/hooks/block-package-installs.sh, make it executable, and register it under beforeShellExecution in .cursor/hooks.json.
#!/usr/bin/env bash
INPUT="$(cat)"
COMMAND="$(echo "$INPUT" | jq -r '.command // ""')"
if echo "$COMMAND" | grep -Eiq '(npm install|npm i|pnpm add|yarn add|bun add|pip install|uv add|composer require|cargo add|go get)'; then
jq -n --arg msg "Package install blocked for review: $COMMAND" '{"permission":"deny","user_message":$msg,"agent_message":"Do not install packages automatically. Explain why the dependency is needed and ask for approval."}'
exit 0
fi
jq -n '{"permission":"allow"}'Warn on expensive background prompts
beforeSubmitPromptScan the prompt for background/batch indicators and recommend the Standard tier.
When to use it
Warn when a prompt appears to request a long-running background task that should not use Fast tier.
Notes
Lightweight cost guardrail; does not block execution.
Save as .cursor/hooks/warn-expensive-background-prompt.sh, make it executable, and register it under beforeSubmitPrompt in .cursor/hooks.json.
#!/usr/bin/env bash
INPUT="$(cat)"
PROMPT="$(echo "$INPUT" | jq -r '.prompt // .message // .text // ""')"
if echo "$PROMPT" | grep -Eiq '(background|batch|ci|all files|entire repo|generate docs|documentation|large refactor|sweep|migrate|mechanical edit)'; then
jq -n '{"agent_message":"Cost warning: this looks like a background/batch task. Prefer Composer Standard instead of Fast unless you need low latency."}'
exit 0
fi
jq -n '{}'Route Composer tier by task type
beforeSubmitPromptClassify a prompt before submission: Fast when latency matters, Standard for batch/throughput work.
When to use it
Apply Fast tier to interactive/pair-programming prompts and Standard tier to background, CI, batch, and document-generation tasks.
Notes
Captures the policy decision. If Cursor exposes tier/model selection in hook output, wire the classifier to that field; otherwise use it as a visible reminder.
Save as .cursor/hooks/route-composer-tier-by-task.sh, make it executable, and register it under beforeSubmitPrompt in .cursor/hooks.json.
#!/usr/bin/env bash
INPUT="$(cat)"
PROMPT="$(echo "$INPUT" | jq -r '.prompt // .message // .text // ""')"
if echo "$PROMPT" | grep -Eiq '(background|batch|ci|code review|generate docs|documentation|large refactor|sweep|mechanical)'; then
jq -n '{"agent_message":"Cost policy: prefer Composer Standard for this background/batch-style task."}'
else
jq -n '{"agent_message":"Cost policy: prefer Composer Fast for this interactive task."}'
fiRun lint and typecheck after edit
afterFileEditAfter edits to JS/TS files, run `npm run lint` and `npm run typecheck` when those scripts exist.
When to use it
Run project checks after Cursor edits code so issues are caught during the agent loop.
Notes
Can be noisy or slow; enable only when project checks are fast enough for interactive coding.
Save as .cursor/hooks/after-edit-checks.sh, make it executable, and register it under afterFileEdit in .cursor/hooks.json.
#!/usr/bin/env bash
INPUT="$(cat)"
FILE_PATH="$(echo "$INPUT" | jq -r '.file_path // ""')"
case "$FILE_PATH" in
*.ts|*.tsx|*.js|*.jsx)
if [ -f package.json ]; then
if jq -e '.scripts.lint' package.json >/dev/null 2>&1; then
npm run lint -- --fix >/tmp/cursor-lint.log 2>&1 || true
fi
if jq -e '.scripts.typecheck' package.json >/dev/null 2>&1; then
npm run typecheck >/tmp/cursor-typecheck.log 2>&1 || true
fi
fi
;;
esac
jq -n '{"agent_message":"Post-edit lint/typecheck hook completed."}'AI overengineering detector
stopCount changed files and detect package/lockfile changes; warn when the solution may be larger than necessary.
When to use it
Warn when a simple task causes too many file changes or package changes.
Notes
Useful for keeping Cursor and Codex from overbuilding simple requests. Adjust the changed-file threshold by project size.
Save as .cursor/hooks/ai-overengineering-detector.sh, make it executable, and register it under stop in .cursor/hooks.json.
#!/usr/bin/env bash
CHANGED_FILES="$(git diff --name-only 2>/dev/null | wc -l | tr -d ' ')"
PACKAGE_CHANGED="false"
git diff --name-only 2>/dev/null | grep -Eiq 'package\.json|package-lock\.json|pnpm-lock\.yaml|yarn\.lock|bun\.lockb' && PACKAGE_CHANGED="true"
if [ "${CHANGED_FILES:-0}" -gt 12 ] || [ "$PACKAGE_CHANGED" = "true" ]; then
jq -n --arg files "$CHANGED_FILES" --arg package "$PACKAGE_CHANGED" '{"followup_message":"Overengineering check: this task changed \($files) files. Package files changed: \($package). Review whether the solution is bigger than necessary before committing."}'
exit 0
fi
jq -n '{}'SEO file validation
afterFileEditCheck edited page files for title, meta description, canonical, Open Graph tags, and JSON-LD schema.
When to use it
Validate SEO essentials on Astro, React, HTML, and landing page files.
Notes
Use as a reminder hook, not a hard blocker. Very useful for lead-gen, rank-and-rent, and local SEO projects.
Save as .cursor/hooks/seo-file-validation.sh, make it executable, and register it under afterFileEdit in .cursor/hooks.json.
#!/usr/bin/env bash
INPUT="$(cat)"
FILE_PATH="$(echo "$INPUT" | jq -r '.file_path // ""')"
[ -f "$FILE_PATH" ] || { jq -n '{}'; exit 0; }
case "$FILE_PATH" in
*.astro|*.tsx|*.jsx|*.html)
CONTENT="$(cat "$FILE_PATH")"
WARNINGS=()
echo "$CONTENT" | grep -Eiq '<title>[^<]{20,70}</title>' || WARNINGS+=("Missing or weak title tag.")
echo "$CONTENT" | grep -Eiq 'name=["'"']description["'"']' || WARNINGS+=("Missing meta description.")
echo "$CONTENT" | grep -Eiq 'rel=["'"']canonical["'"']' || WARNINGS+=("Missing canonical tag.")
echo "$CONTENT" | grep -Eiq 'property=["'"']og:title["'"']' || WARNINGS+=("Missing Open Graph title.")
echo "$CONTENT" | grep -Eiq 'application/ld\+json' || WARNINGS+=("Missing JSON-LD schema markup.")
if [ ${#WARNINGS[@]} -gt 0 ]; then
printf '%s\n' "${WARNINGS[@]}" | jq -R . | jq -s '{"agent_message":"SEO warnings:\n" + (join("\n"))}'
exit 0
fi
;;
esac
jq -n '{}'LocalBusiness schema check
afterFileEditScan local landing pages for JSON-LD containing LocalBusiness, Service, areaServed, and contact fields.
When to use it
Confirm local pages include LocalBusiness or service-area schema when appropriate.
Notes
Can overlap with the SEO Site JSON-LD check, but this is local-specific.
Save as .cursor/hooks/localbusiness-schema-check.sh, make it executable, and register it under afterFileEdit in .cursor/hooks.json.
#!/usr/bin/env bash
set -euo pipefail
INPUT="$(cat)"
FILE_PATH="$(echo "$INPUT" | jq -r '.file_path // ""')"
[ -f "$FILE_PATH" ] || { jq -n '{}'; exit 0; }
case "$FILE_PATH" in
*.astro|*.html|*.jsx|*.tsx|*.md|*.mdx)
;;
*)
jq -n '{}'
exit 0
;;
esac
CONTENT="$(cat "$FILE_PATH")"
WARNINGS=()
echo "$CONTENT" | grep -Eiq 'application/ld\+json' || WARNINGS+=("Missing JSON-LD script.")
echo "$CONTENT" | grep -Eiq 'LocalBusiness|Organization|ProfessionalService|HomeAndConstructionBusiness|Service' || WARNINGS+=("Missing LocalBusiness/Service-style schema type.")
echo "$CONTENT" | grep -Eiq 'telephone|phone' || WARNINGS+=("Schema may be missing telephone.")
echo "$CONTENT" | grep -Eiq 'address|areaServed|serviceArea' || WARNINGS+=("Schema may be missing address or service area.")
if [ ${#WARNINGS[@]} -gt 0 ]; then
printf '%s\n' "${WARNINGS[@]}" | jq -R . | jq -s --arg file "$FILE_PATH" '{"agent_message":"Local schema warnings in " + $file + ":\n" + (join("\n"))}'
exit 0
fi
jq -n '{}'GA4 tag presence check
afterFileEditScan layout and page files for GA4, GTM, or configured measurement/container IDs.
When to use it
Warn if layout or landing page changes remove or omit GA4/GTM tracking.
Notes
Configure allowed measurement IDs in a project config file.
Save as .cursor/hooks/ga4-tag-presence-check.sh, make it executable, and register it under afterFileEdit in .cursor/hooks.json.
#!/usr/bin/env bash
set -euo pipefail
INPUT="$(cat)"
FILE_PATH="$(echo "$INPUT" | jq -r '.file_path // ""')"
[ -f "$FILE_PATH" ] || { jq -n '{}'; exit 0; }
case "$FILE_PATH" in
*layout*|*.astro|*.html|*.jsx|*.tsx)
;;
*) jq -n '{}'; exit 0 ;;
esac
CONTENT="$(cat "$FILE_PATH")"
HAS_ANALYTICS="false"
echo "$CONTENT" | grep -Eiq 'G-[A-Z0-9]{6,}|GTM-[A-Z0-9]+|gtag\(|dataLayer' && HAS_ANALYTICS="true"
if [ "$HAS_ANALYTICS" = "false" ]; then
jq -n --arg file "$FILE_PATH" '{"agent_message":"Analytics warning: no GA4/GTM-style tag detected in \($file). Confirm analytics is included elsewhere before publishing."}'
exit 0
fi
jq -n '{}'CTA consistency check
afterFileEditScan edited pages for mixed CTA language such as call now vs apply online vs get quote.
When to use it
Detect inconsistent CTAs across hero, buttons, forms, and sticky bars.
Notes
Helps keep landing pages focused on one conversion action.
Save as .cursor/hooks/cta-consistency-check.sh, make it executable, and register it under afterFileEdit in .cursor/hooks.json.
#!/usr/bin/env bash
set -euo pipefail
INPUT="$(cat)"
FILE_PATH="$(echo "$INPUT" | jq -r '.file_path // ""')"
[ -f "$FILE_PATH" ] || { jq -n '{}'; exit 0; }
case "$FILE_PATH" in
*.astro|*.html|*.jsx|*.tsx|*.md|*.mdx)
;;
*)
jq -n '{}'
exit 0
;;
esac
CONTENT="$(tr '[:upper:]' '[:lower:]' < "$FILE_PATH")"
CALL_COUNT="$(echo "$CONTENT" | grep -Eo '(call now|call today|speak with|talk to|tel:)' | wc -l | tr -d ' ')"
FORM_COUNT="$(echo "$CONTENT" | grep -Eo '(get quote|request quote|apply online|submit form|get started|free estimate)' | wc -l | tr -d ' ')"
BOOK_COUNT="$(echo "$CONTENT" | grep -Eo '(book now|schedule|appointment|calendar)' | wc -l | tr -d ' ')"
TYPES=0
[ "${CALL_COUNT:-0}" -gt 0 ] && TYPES=$((TYPES+1))
[ "${FORM_COUNT:-0}" -gt 0 ] && TYPES=$((TYPES+1))
[ "${BOOK_COUNT:-0}" -gt 0 ] && TYPES=$((TYPES+1))
if [ "$TYPES" -gt 1 ]; then
jq -n --arg file "$FILE_PATH" --arg call "$CALL_COUNT" --arg form "$FORM_COUNT" --arg book "$BOOK_COUNT" '{
"agent_message":"CTA consistency warning in \($file): mixed CTA intents detected. call=\($call), form=\($form), booking=\($book). Confirm the page has one primary conversion action."
}'
exit 0
fi
jq -n '{}'Generate docs after docs-related edits
afterFileEditDetect docs-relevant file paths and remind or run a docs generation script when configured.
When to use it
Keep documentation fresh when Cursor modifies API routes, components, or configuration files.
Notes
Useful for SaaS/tool projects where docs drift quickly. Good candidate for Standard-tier/background work rather than Fast-tier interactive work.
Save as .cursor/hooks/docs-after-edit.sh, make it executable, and register it under afterFileEdit in .cursor/hooks.json.
#!/usr/bin/env bash
set -euo pipefail
INPUT="$(cat)"
FILE_PATH="$(echo "$INPUT" | jq -r '.file_path // ""')"
if ! echo "$FILE_PATH" | grep -Eiq '(src/api|routes|components|lib|config|schema|types|README|docs)'; then
jq -n '{}'
exit 0
fi
if [ -f package.json ] && jq -e '.scripts.docs' package.json >/dev/null 2>&1; then
npm run docs >/tmp/cursor-docs.log 2>&1 || {
jq -n '{"agent_message":"Docs script failed. Review /tmp/cursor-docs.log."}'
exit 0
}
jq -n '{"agent_message":"Docs script ran after a docs-relevant edit."}'
exit 0
fi
jq -n --arg file "$FILE_PATH" '{"agent_message":"Docs-relevant file changed: \($file). Review README/docs/API notes before completing this task."}'Commit message generator
stopInspect git diff names and return a conventional-style commit message suggestion.
When to use it
Suggest a concise commit message based on changed files.
Notes
Helpful when using Cursor for multiple small edits. Use as a suggestion only; review before committing.
Save as .cursor/hooks/commit-message-generator.sh, make it executable, and register it under stop in .cursor/hooks.json.
#!/usr/bin/env bash
set -euo pipefail
FILES="$(git diff --name-only 2>/dev/null || true)"
[ -n "$FILES" ] || { jq -n '{}'; exit 0; }
TYPE="chore"
SCOPE="project"
if echo "$FILES" | grep -Eiq '(src/pages|src/routes|app/)'; then TYPE="feat"; SCOPE="pages"; fi
if echo "$FILES" | grep -Eiq '(components|ui)'; then TYPE="feat"; SCOPE="ui"; fi
if echo "$FILES" | grep -Eiq '(test|spec)'; then TYPE="test"; SCOPE="tests"; fi
if echo "$FILES" | grep -Eiq '(README|docs)'; then TYPE="docs"; SCOPE="docs"; fi
if echo "$FILES" | grep -Eiq '(package|config|wrangler|vite|astro|next)'; then TYPE="chore"; SCOPE="config"; fi
MSG="$TYPE($SCOPE): update project files"
jq -n --arg msg "$MSG" --arg files "$FILES" '{
"followup_message":"Suggested commit message: `\($msg)`\n\nChanged files:\n\($files)"
}'Notify when agent stops
stopOn stop, return a follow-up message and optionally trigger a desktop notification.
When to use it
Notify when Cursor finishes a task and remind the user to review diff/tests before committing.
Notes
Use macOS `osascript` or Linux `notify-send` depending on your environment.
Save as .cursor/hooks/session-summary.sh, make it executable, and register it under stop in .cursor/hooks.json.
#!/usr/bin/env bash
# macOS variant — swap osascript for notify-send on Linux
jq -n '{"followup_message":"Agent task completed. Review the diff, run tests, and commit if everything looks good."}'
if command -v osascript >/dev/null 2>&1; then
osascript -e 'display notification "Cursor agent task completed" with title "Cursor"'
elif command -v notify-send >/dev/null 2>&1; then
notify-send "Cursor" "Agent task completed"
fiSitemap regeneration hook
afterFileEditDetect route/page changes and run a sitemap script when present.
When to use it
Regenerate sitemap when routes, pages, or content collections change.
Notes
Helpful for city/state landing page generators and static SEO sites. May be unnecessary if sitemap generation happens automatically in the build.
Save as .cursor/hooks/sitemap-regeneration-hook.sh, make it executable, and register it under afterFileEdit in .cursor/hooks.json.
#!/usr/bin/env bash
INPUT="$(cat)"
FILE_PATH="$(echo "$INPUT" | jq -r '.file_path // ""')"
if echo "$FILE_PATH" | grep -Eiq '(src/pages|src/content|routes|astro\.config|sitemap)'; then
if [ -f package.json ] && jq -e '.scripts["sitemap"]' package.json >/dev/null 2>&1; then
npm run sitemap >/tmp/cursor-sitemap.log 2>&1 || true
jq -n '{"agent_message":"Sitemap regeneration script ran after route/content change."}'
else
jq -n '{"agent_message":"Route/content changed. Confirm sitemap generation before publishing."}'
fi
exit 0
fi
jq -n '{}'Registering hooks
Each script is wired up in .cursor/hooks.json under the matching event. Multiple hooks can share an event — they run in order:
{
"hooks": {
"afterFileEdit": [
{ "command": ".cursor/hooks/seo-file-validation.sh" }
],
"beforeShellExecution": [
{ "command": ".cursor/hooks/block-package-installs.sh" }
]
}
}Make scripts executable with chmod +x .cursor/hooks/*.sh. Scripts read JSON from stdin and most need jq installed.
Known limits & gotchas — Cursor Hooks
Real-world quirks that bite if you don't know about them. Not deal-breakers — just things to design around.
- Hooks execute on every matching event — afterFileEdit fires on each file the agent touches. Keep scripts fast and exit early for irrelevant paths, or the agent loop slows down.
- Deny hooks can stall the agent — beforeShellExecution hooks that return permission: deny stop the command. Make the agent_message explicit so the agent knows how to proceed.
- jq and shell tools must be installed — These scripts assume jq, grep, and git are available. On minimal environments, install them first or the hook silently no-ops.
- Tier-routing is policy, not an API — Cost hooks surface a recommendation; Cursor may not expose programmatic tier switching. Treat them as visible reminders unless the hook output supports tier fields.
Universal Learner Brain Guides
These guides apply to all platforms — use your .cursor/hooks/ path wherever they reference rule/skill locations.
Official Documentation
This template was last reviewed in June 2026 against the official Cursor Hooks documentation. Spot something out of date? Let us know.