Back to Cursor Template
    13 hooks · 4 lifecycle events
    Updated June 2026

    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

    Every hook executes shell code on your machine inside the agent loop. Read each script before you register it, and start with the lower-risk, reminder-only hooks before enabling ones that block commands or run builds.

    Filter by event

    Filter by category

    Showing 13 of 13 hooks

    Block surprise package installs

    beforeShellExecution
    Medium risk
    Safe Defaults
    P0
    Package Management
    Security
    Cost Optimization

    Deny 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"}'
    Source

    Warn on expensive background prompts

    beforeSubmitPrompt
    Low risk
    Cost Optimization
    P0
    Cost Optimization

    Scan 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 '{}'
    Source

    Route Composer tier by task type

    beforeSubmitPrompt
    Medium risk
    Cost Optimization
    P1
    Cost Optimization
    Workflow

    Classify 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."}'
    fi
    Source

    Run lint and typecheck after edit

    afterFileEdit
    Medium risk
    Agent Quality
    P1
    Quality
    Workflow

    After 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."}'
    Source

    AI overengineering detector

    stop
    Medium risk
    Agent Quality
    P1
    Quality
    Workflow
    Package Management

    Count 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 '{}'
    Source

    SEO file validation

    afterFileEdit
    Medium risk
    SEO Site
    P0
    Quality

    Check 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 '{}'
    Source

    LocalBusiness schema check

    afterFileEdit
    Medium risk
    Local SEO / GMB
    P0
    Quality

    Scan 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 '{}'
    Source

    GA4 tag presence check

    afterFileEdit
    Medium risk
    Analytics / Tracking
    P0
    Quality

    Scan 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 '{}'
    Source

    CTA consistency check

    afterFileEdit
    Low risk
    Lead-Gen Funnel
    P1
    Quality

    Scan 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 '{}'
    Source

    Generate docs after docs-related edits

    afterFileEdit
    Low risk
    Agent Quality
    P2
    Documentation
    Quality

    Detect 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."}'
    Source

    Commit message generator

    stop
    Low risk
    Agent Quality
    P2
    Workflow
    Logging

    Inspect 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)"
    }'
    Source

    Notify when agent stops

    stop
    Low risk
    Agent Quality
    P2
    Notifications
    Workflow

    On 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"
    fi
    Source

    Sitemap regeneration hook

    afterFileEdit
    Low risk
    SEO Site
    P2
    Quality
    Documentation

    Detect 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 '{}'
    Source

    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.

    Official Documentation

    This template was last reviewed in June 2026 against the official Cursor Hooks documentation. Spot something out of date? Let us know.