Cursor reads rule files from .cursor/rules and injects them into the model's context, so your project conventions survive between chats. That part is easy. The hard part is writing a rule the model actually follows. Most rules in the wild fail in one of three ways: they are silently ignored (wrong file format), they load at the wrong time (wrong activation type), or they are written in a style that measurably degrades current models. This guide covers all three: the .mdc anatomy, the four rule types, and what the research says belongs inside a rule.
What a Cursor rule is, and when you need one
A Cursor rule is a markdown file with YAML frontmatter, saved with the .mdc extension inside .cursor/rules/ at your project root. When a rule activates, Cursor prepends its body to the model's context for that request. Rules live in the repo, so the whole team shares them, and nested .cursor/rules directories inside subfolders let a monorepo scope rules per package.
Write a rule when you catch yourself correcting the AI on the same thing twice: it keeps suggesting class components, it keeps importing from the wrong path, it keeps writing raw SQL where your codebase uses a query builder. Rules are for facts the model cannot discover on its own and conventions that differ from framework defaults.
Do not write a rule for things the model already knows (how npm install works) or things a linter enforces better (formatting, import order). Every rule spends context tokens; spend them on project-specific signal.
Two housekeeping notes. The legacy single .cursorrules file at the repo root still works but is deprecated; migrate it into .cursor/rules/. And the single most common failure: a plain .md file inside .cursor/rules is silently ignored. No warning, no error. The extension must be .mdc, and the file needs frontmatter.
The anatomy of an .mdc file
Three frontmatter fields, then a markdown body. Here is a complete, working rule:
---
description: React component conventions for the design system
globs:
- "src/components/**/*.tsx"
alwaysApply: false
---
# Component conventions
- Use function components with named exports. Default exports break
our barrel-file codegen.
- Style with Tailwind utilities only. Do not add CSS Modules; the
build pipeline strips them.
- Every component accepts a className prop and merges it via cn()
from src/lib/utils.
- Data fetching lives in hooks under src/hooks, never inside
components, because components are rendered in Storybook without
a network layer.
After edits, run: npm run lint:components (it enforces the export rule).
What each field does:
description: one sentence saying what the rule covers. For Agent Requested rules (below) this is the text the model reads to decide whether the rule is relevant, so write it like a tool description: "Use when writing or editing React components."globs: file patterns that trigger the rule. When a matching file is in the current context, the rule auto-attaches.alwaysApply:trueloads the rule into every request, regardless of the other two fields.
The body is plain markdown. Keep it instructions, not documentation: short imperative bullets, concrete file paths, runnable commands, and a reason attached to every prohibition.
The four rule types, and how to choose scope
The frontmatter combination determines when the rule loads:
| Type | Frontmatter | When it loads |
|---|---|---|
| Always | alwaysApply: true | Every request. globs and description are ignored. |
| Auto Attached | globs set, alwaysApply: false | A file matching the glob is in context |
| Agent Requested | description set, no globs | The model decides, based on the description |
| Manual | none of the above | Only when you invoke it with @rule-name |
Auto Attached is the workhorse. Your React conventions load when a .tsx file is in play, your API conventions load in route handlers, your test conventions load in *.test.ts, and none of them tax unrelated requests. Reserve Always for a handful of project-wide fundamentals (stack declaration, non-negotiable security constraints), because an Always rule bills tokens on every single interaction.
One trap worth calling out explicitly: with alwaysApply: true, Cursor ignores your globs. If you set both, the rule loads everywhere and the scoping you thought you had does not exist. If you meant path scoping, set alwaysApply: false and keep the globs. (This exact combination is one of the checks in our linter.)
Prefer several narrow rules over one broad one. One rule per concern, each scoped to the files where it matters, keeps every individual context injection small and relevant.
What to put in, and what to leave out
This is where most rules quietly lose value. We went through the research behind ~30 context-file anti-patterns in our context-file linter writeup; here is the short version applied to Cursor rules.
Put in:
- Non-discoverable facts. Pitfalls, rationale, and decisions the model cannot infer from the code: "we pin Zod to v3 because v4 breaks our OpenAPI generator."
- Deviations from defaults. The model assumes framework conventions; document only where you differ.
- Commands. "Run npx vitest run before claiming a fix works" changes behavior. Agents follow command-anchored rules and skip prose style references.
- Prohibitions with reasons. "Never use CSS Modules, because the build strips them" generalizes; a bare "NEVER use CSS Modules" does not, and models increasingly discount it.
Leave out:
- Style-guide prose without a command. "Follow the Airbnb style guide" gets skipped. Give the lint command or delete the line; never send an LLM to do a linter's job.
- Directory-tree dumps. The ETH Zurich AGENTS.md benchmark found agents ignore pasted folder structures while paying roughly 14-22% more reasoning tokens for them. The agent explores the tree itself.
- Forcing language. "CRITICAL: You MUST..." was written to fix under-triggering on 2024-era models. Current models over-trigger on it; Anthropic now recommends plain "Use X when...", and Cursor documented a production regression on GPT-5 caused by a "be THOROUGH" instruction.
- Emphasis inflation. If eight bullets are ALL-CAPS IMPORTANT, none of them are. Emphasis works by contrast.
- Hedged language. "Try to", "if possible", "ideally" tell the model the rule is optional, and it obliges. State hard rules plainly.
- Anything past 500 lines. Cursor's own docs recommend keeping rules under 500 lines. Past that, split into focused, scoped rules.
A worked example: Next.js App Router plus Supabase
Here is a realistic Auto Attached rule for API route handlers in a Next.js and Supabase codebase:
---
description: Conventions for App Router API route handlers
globs:
- "app/api/**/route.ts"
alwaysApply: false
---
# API route conventions
- Validate the request body with a Zod schema from src/schemas
before any I/O. Return 400 with the flattened error, never a
raw Zod error object (it leaks internal paths).
- Create the Supabase client with createServerClient from
src/lib/supabase/server. Do not import the browser client here;
it has no access to auth cookies.
- Mutating handlers call validateOrigin(req) first. Webhooks are
the only exception; they verify provider signatures instead.
- Return NextResponse.json with an explicit status. Bare
Response objects bypass our logging middleware.
Verify with: npx vitest run __tests__/api
Notice what makes it work: every line is project-specific, every prohibition carries its reason, the scope is exactly the files where the conventions apply, and it ends with a runnable verification command. Fifteen lines of body, and none of them are things the model would have guessed.
Common mistakes
- Saving as
.md. Silently ignored. Use.mdcwith frontmatter. alwaysApply: truewith globs. The globs are ignored; the rule loads everywhere.- One mega Always rule. Every request pays for all of it; most of it is irrelevant to any given task.
- A vague description on an Agent Requested rule. "General project guidance" gives the model nothing to match against, so the rule never activates. Describe the trigger: "Use when writing database migrations."
- Duplicating your linter. ESLint and Prettier already enforce formatting deterministically and for free.
- Secrets in rules. Rules are committed and shared. Rules files were also the vector for the 2025 "Rules File Backdoor" attack, where invisible Unicode smuggled instructions past code review, so treat third-party rules you paste in as untrusted input and scan them.
- Letting rules go stale. A rule that contradicts the current codebase is worse than no rule; the model burns tokens trying to reconcile them.
How to validate a rule
Two checks, one static and one empirical.
Static: lint your rule in the browser, or offline with the CLI:
npx promptarch lint .cursor/rules/api-conventions.mdc
The linter verifies the frontmatter is present and well-formed, flags the alwaysApply-plus-globs trap, enforces the 500-line budget, scans for hidden Unicode and leaked secrets, and scores the writing against the anti-pattern research above. It is free, needs no account, and runs fully offline.
Empirical: open Cursor, ask for a task the rule should govern, and check the context panel to confirm the rule attached. Then look at the output. If the model ignored an instruction, the fix is usually specificity: add the reason, add the command, or narrow the rule so the instruction is not buried.
Generate one free
If you would rather start from a working draft than a blank file, PromptArch's free Cursor rule generator asks you the structured questions (stack, activation type, globs, conventions, restrictions) and generates a complete .mdc with the frontmatter already chosen correctly. Your first build is free with no signup on the try page. And if you already have rules in your repo, paste them into the linter and see what they score.