Cursor Rules — Make .cursorrules Actually Earn Its Keep

Rules anatomy, when to use .cursor/rules/*.mdc vs the legacy .cursorrules, and how to write rules the model actually follows.

What this covers

How to write Cursor rules that actually change model behavior, instead of the 200-line “be helpful, write clean code” walls that get politely ignored. The rules system has two shapes in current Cursor: project rules under .cursor/rules/*.mdc with frontmatter (globs, scope, alwaysApply), and the legacy single .cursorrules file in the repo root. This guide covers both, when to pick which, what to put in them, and the rules anti-patterns that produce zero behavior change.

Key concepts:

  • Project rules: .cursor/rules/*.mdc — multiple scoped files with frontmatter.
  • Legacy .cursorrules: single file, still supported, no scoping — convenient for tiny projects.
  • Glob targeting: rules only attach when files matching the glob are in context.
  • alwaysApply: true: makes a rule attach to every request — use sparingly.

Who this is for

Cursor users whose agent keeps making the same mistake — wrong import style, wrong test runner, wrong file location — and who have realized that re-prompting it every chat is not scalable. Especially valuable on teams: the shared rules file is how project conventions stop relying on tribal memory. If you have not done basic onboarding yet, run Cursor for beginners first.

When to reach for it

Reach for rules when you see Cursor making the same correctable mistake twice in one week — that is your signal the constraint belongs in a rule, not in a prompt. Also when a teammate’s Cursor session produces visibly different code style than yours; rules are how you sync defaults. They lose to inline @File mentions when the constraint is one-off; don’t write a rule for a one-time task.

Before you start

  • A .cursor/ folder at the repo root (Cursor creates it on first use, or mkdir .cursor/rules).
  • A list of specific complaints you have with Cursor’s current output — these become rule candidates. Vague complaints don’t make good rules.
  • Knowledge of your repo’s globs: which paths are tests, which are server code, which are generated. Rules attach by glob.
  • A teammate or two to dogfood the rules with. Solo-author rules tend to be over-tuned to one person’s style.

Step by step

  1. Decide format. New projects: use .cursor/rules/*.mdc. Tiny solo projects: .cursorrules is fine. Teams: definitely .cursor/rules/ so you can scope and split.
  2. Create .cursor/rules/typescript.mdc with frontmatter and a focused body. Keep it under 60 lines; long rules get truncated or skimmed by the model:
---
description: TypeScript conventions for src/
globs: ["src/**/*.ts", "src/**/*.tsx"]
alwaysApply: false
---

# TypeScript rules

- Use named exports. Default exports are banned in `src/` except `pages/`.
- All async functions must have an explicit return type.
- Prefer `unknown` over `any`. If `any` is unavoidable, leave a `// eslint-disable-next-line @typescript-eslint/no-explicit-any` with a one-line reason.
- Imports: absolute paths via `@/` for anything in `src/`, relative for siblings.
- Errors thrown inside HTTP handlers must extend `AppError`.
  1. Add a second rule file for tests: .cursor/rules/tests.mdc with globs: ["**/*.test.ts", "**/*.spec.ts"]. Keep test-only conventions out of the general TypeScript rule.
  2. Add a forbid.mdc with alwaysApply: true for the small set of repo-wide hard “never” rules — paths that must not be edited, secrets that must not be inlined, commands that must not be run.
  3. Verify rules are loading. Open Cursor settings → rules panel, confirm each .mdc file is listed. If a rule should be active but doesn’t show, check the glob and frontmatter for typos (rules not loading troubleshooting).
  4. Run a representative prompt — one that previously produced the wrong pattern — and check the output. If the rule didn’t help, the rule is too vague or too long. Tighten and retry.
  5. Commit .cursor/rules/ to the repo. Treat it like any other source file in code review.

A rule that produces honest output

The “be helpful, write clean code” rule is useless. This pattern is what actually moves model behavior — concrete, file-scoped, with examples:

---
description: API handler conventions
globs: ["src/api/**/*.ts"]
alwaysApply: false
---

# API handlers

All handlers in `src/api/` must:

1. Validate input with `zod` schemas defined in the same file. No inline `as` casts.
2. Wrap business logic calls in try/catch and re-throw as `AppError` with a stable error code.
3. Return responses via `respond.ok(data)` / `respond.error(code, message)` — never construct Response objects directly.
4. Log entry with `logger.handler(req, "name")` at the top of the function.

Example shape:

```ts
export const POST = async (req: Request) => \{
  logger.handler(req, "createOrder");
  const input = OrderSchema.parse(await req.json());
  try \{
    const order = await orders.create(input);
    return respond.ok(order);
  \} catch (e) \{
    throw new AppError("ORDER_CREATE_FAILED", \{ cause: e \});
  \}
\};

Concrete > aspirational. The model copies what you show, not what you tell.

## Quality check

- Each `.mdc` file is focused on one concern. If a rule covers "TypeScript and tests and API and styling," split it.
- Globs are precise — a `src/**/*.ts` rule doesn't accidentally attach to `node_modules` (Cursor ignores those by default but check).
- `alwaysApply: true` is used on at most one or two files. Every always-apply rule eats context budget.
- Rule bodies are under ~60 lines each. Longer means the model treats them like documentation, not instructions.
- A representative test prompt produces the desired behavior. If not, rewrite the rule, don't add another.
- Teammates' Cursor sessions produce comparable code style to yours after rules are committed.

## How to reuse this workflow

- Keep a `RULES_CHANGELOG.md` in `.cursor/` noting why each rule exists. Future-you will want to know.
- When Cursor makes the same mistake twice in two weeks, add a rule. Same mistake once is a prompt fix; twice is a rule.
- Quarterly prune: delete rules that no longer apply (framework upgraded, pattern changed). Stale rules silently mislead the model.
- For multi-language repos, split rules by language file and by purpose — `python.mdc`, `python-tests.mdc`, `migrations.mdc` — not one mega-file.

## Recommended workflow

Notice repeat mistake → write a focused `.mdc` with concrete examples → set a precise glob → test on a representative prompt → commit.

## Common mistakes

- 200-line walls of aspirational text — "be a senior engineer, write clean and maintainable code." The model ignores them.
- Overusing `alwaysApply: true`. Every always-apply rule shrinks the context budget for actual code.
- Vague rules ("write good tests") with no example. Show the shape you want; the model imitates.
- One mega-rule covering the whole project. Split by concern; the model attends better to focused files.
- Forgetting that rules attach only when files matching the glob are in context — if no `src/api/` file is open, the API rule is dormant.
- Conflicting rules across project, workspace, and user files. Symptoms are "Cursor ignores my rules" — see [Cursor rules not loaded](/en/articles/cursor-rules-not-loaded/) and [Cursor rules not loading](/en/articles/cursor-rules-not-loading/).

## FAQ

- **Should I migrate `.cursorrules` to `.cursor/rules/`?**: If the file is over 50 lines or has multiple concerns, yes. Tiny single-purpose files can stay as `.cursorrules`.
- **Does the model see all my rules every prompt?**: No — only rules whose glob matches a file in context, plus any `alwaysApply: true` rules.
- **Can rules reference other rules?**: Not via include, but you can mention "see styling.mdc" in prose; the model will look it up if styling.mdc's glob also matches.
- **Why don't my rules show up in the Cursor sidebar?**: Frontmatter typo (most often a missing closing `---`), invalid YAML, or the file is in the wrong folder. Check [Cursor rules not loading](/en/articles/cursor-rules-not-loading/).

## Related

- [Cursor for beginners — 30 minutes to a working loop](/en/articles/cursor-getting-started/)
- [Cursor indexing tutorial](/en/articles/cursor-indexing-tutorial/)
- [Cursor rules not loaded](/en/articles/cursor-rules-not-loaded/)
- [AI coding context management via project files](/en/articles/feed-project-reports-to-agents/)

Tags: #AI coding #Tutorial #Cursor