Type-Safe Cursor Hooks with the cursor-hooks Package

John Lindquist
InstructorJohn Lindquist

Social Share Links

Tweet

Writing hooks without type safety means you're guessing at property names, relying on documentation for payload structure, and hoping your response format is correct. Every hook becomes an exercise in trial and error, with no editor assistance to guide you.

This lesson introduces the cursor-hooks package, which provides complete TypeScript definitions for all hook payloads and responses. Combined with Bun's elegant Bun.stdin.json() method for reading input, you'll write hooks with full autocomplete support, compile-time error checking, and absolute confidence that your code is correct.

The Power of Types

  • Autocomplete Everything: See available properties as you type, with no need to reference documentation.
  • Catch Errors Early: TypeScript catches typos and invalid property access before you run your code.
  • Self-Documenting Code: Type definitions serve as inline documentation, making hooks easier to understand and maintain.
  • Elegant Input Handling: Bun's Bun.stdin.json() reads and parses hook input in a single, clean line of code.

Building a Type-Safe Hook

The workflow is simple and produces reliable, maintainable code:

  1. Install Dependencies: Add the cursor-hooks package to your Bun project for complete type definitions.
  2. Read Typed Input: Use await Bun.stdin.json() and apply the appropriate payload type (e.g., BeforeSubmitPromptPayload).
  3. Build Typed Output: Create your response object with the correct response type (e.g., BeforeSubmitPromptResponse).
  4. Implement Logic: Write conditional logic based on the typed input—your editor will guide you with autocomplete at every step.
  5. Test Immediately: Test the hook directly in Cursor's chat interface to see your logic in action.

Commands

Install the package providing TypeScript types for Cursor hooks.

bun install cursor-hooks

Prompts

A test prompt that will be blocked by the hook's logic.

jump

A test prompt that includes the required keyword to be allowed by the hook.

allow jump

Example Implementation

This before-submit-prompt.ts script shows the elegance of type-safe hooks. Notice how the types guide the entire implementation—from reading the payload to building the response.

import type { BeforeSubmitPromptPayload, BeforeSubmitPromptResponse } from "cursor-hooks";

const input: BeforeSubmitPromptPayload = await Bun.stdin.json();

const output: BeforeSubmitPromptResponse = {
  continue: input.prompt.includes("allow"),
};

console.log(JSON.stringify(output, null, 2));

With types in place, your editor becomes a powerful assistant, showing you exactly what properties are available on the input and what shape your output must take. This transforms hook development from guesswork into a guided, confident process.

[00:00] So back in our hooks directory where we initialized a blank bun project, I'll clear this out real quick, and I'm going to bun install a project I put together called cursor-hooks, which has typings for all of the hooks based on the documentation. And for a little bit more organization, let's rename index to before submit prompt. Then in our hooks, rename this to before submit prompt. Then in our before submit prompt script, we can now import some types like the before submit prompt response, and we can type our output to that, meaning that we'll get IntelliSense for continue, so we can be much more confident in the hooks we're building. Now the main reason we're using bun is because bun has something called bun.standardIn() with a JSON method on it, which converts the standard in to JSON in this single line.

[00:57] So the input can be this, and then we can type the input as a beforeSubmitPrompt payload. We'll let that automatically import. And now we can define continue as if the input prompt, and you'll see we get autocomplete for all this, includes allow, then continue will be true. If it doesn't include allow continue will be false. So open our agent we'll say jump, hit enter, it's blocked.

[01:24] We'll say allow jump, hit enter, and now you see it starts trying to do this action. We'll just reject this sample use case And you'll see we now have a typed version of the input and the output where we can easily add conditions around the prompt that's being submitted and whether or not to allow that prompt to actually continue.