Overview

A .cursorrules file is a persistent system prompt that Cursor injects into every AI conversation in the project. A well-written rules file produces code that fits the codebase from the first suggestion, without per-session re-prompting. The principles that make a system prompt effective are the same ones in system-prompts; this guide applies them to a TypeScript project.

Prerequisites

  • Cursor IDE installed.
  • A TypeScript project with a tsconfig.json. The rules reference TypeScript-specific conventions.
  • Familiarity with the project’s style, testing framework, and folder structure. Rules you cannot verify are rules that drift.

Steps

1. Create the file

touch .cursorrules

Place it at the repo root. Cursor reads it automatically when the project is open. Commit it with the code; it is project documentation.

2. Open with a mission statement

Start with one or two sentences that name the project, its purpose, and the main stack.

This is a Next.js 15 + TypeScript e-commerce storefront.
The codebase uses the App Router, Tailwind CSS, Prisma ORM, and Postgres.

Keep it specific. “This is a web project” adds no signal. See prompt-design for the general rule on specificity.

3. Set voice and style rules

Voice rules:
- Lead with the implementation, then the explanation.
- Prefer short, named functions over long anonymous callbacks.
- No commented-out code in commits.
- No TODO comments without a ticket reference.

Match the voice rules to what the team actually enforces in code review. If the team does not care about TODO comments, omit that rule.

4. Define TypeScript conventions

TypeScript rules:
- Strict mode is on. Never use `any`; use `unknown` and narrow it.
- Prefer `interface` for public API shapes; use `type` for unions and mapped types.
- Export types from `types.ts` in each feature folder.
- Use `zod` for runtime validation at API boundaries.
- Avoid `as` casts; if a cast is unavoidable, add a comment explaining why.

These map directly to the patterns in typescript-strict-mode. See typescript for the full type-level conventions.

5. Set folder and file conventions

File conventions:
- React components live in `src/components/<ComponentName>/index.tsx`.
- Server actions live in `src/app/actions/<feature>.ts`.
- Database queries live in `src/lib/db/<entity>.ts`.
- One component per file. Do not export multiple components from one file.
- Barrel files (`index.ts`) are allowed only in `src/components`.

Concrete paths prevent the AI from placing files in unexpected locations.

6. Define test expectations

Testing rules:
- Tests live next to the file they test: `foo.ts` and `foo.test.ts`.
- Use Vitest, not Jest.
- Test behavior, not implementation. Do not test private methods.
- Every function exported from `src/lib` must have at least one test.
- Use `describe` blocks to group related tests; keep `it` descriptions in plain English.

See testing for the broader testing philosophy.

7. Add a complete example

Examples anchor the rules in concrete output. Include at minimum one before/after pair.

Example: API route with validation

// Good
import { z } from "zod"
const schema = z.object({ email: z.string().email() })
export async function POST(req: Request) {
  const body = schema.safeParse(await req.json())
  if (!body.success) return Response.json({ error: body.error.flatten() }, { status: 400 })
  // ...
}

// Avoid: no runtime validation
export async function POST(req: Request) {
  const { email } = await req.json()
  // email is any; no validation
}

8. Scope and length

Keep the file under 200 lines. Rules past that point compete for attention and dilute signal. Prioritize the conventions that are:

  1. Hard to enforce via a linter.
  2. Frequently violated by AI-generated code.
  3. Specific to this project (not just TypeScript best practices generally).

Verify it worked

  1. Open a new Cursor chat (not a file-level inline suggestion) and ask: “How should I structure a new API route in this project?”
  2. Confirm the answer references the folder conventions and Zod validation from the rules.
  3. Ask it to write a test for a utility function. Confirm it uses Vitest and places the test file correctly.
  4. If the AI ignores the rules, the rules file may not be at the repo root, or the project has both .cursorrules and .cursor/rules/ (the newer format). Use one format; do not mix.

Common errors

  • The AI ignores the rules. The file is in a subfolder. Move it to the project root.
  • Rules conflict with each other. “Use functional components” and “class components are fine for stateful logic” cannot coexist. Resolve the conflict before committing.
  • Rules are too abstract. “Write clean code” is not a rule; “functions must be under 30 lines” is. Replace abstractions with measurable criteria.
  • Rules become stale as the stack evolves. Treat the file like a CLAUDE.md: review it when the framework, test runner, or style guide changes. See claude-code for the anchor-file maintenance pattern.
  • .cursorrules format deprecated warning. Cursor is migrating to .cursor/rules/<name>.mdc. The old format still works; migrate when the team updates Cursor.