The file the examples are written for
If you search for Cursor rules you will find a great many .cursorrules files: one file at the repository root, a flat list of instructions, often hundreds of lines copied from a style guide. Whole curated collections of them exist.
Cursor's current Rules documentation lists four kinds of rule, and .cursorrules is not among them. That does not mean your existing file stopped working, and this post is not going to claim it did. It does mean anything written against it was written for a version of the tool the docs no longer describe, and that the mechanisms most worth using now did not exist when it was written.
| Kind | Where it lives |
|---|---|
| Project rules | .cursor/rules/*.mdc, committed to the repository |
| User rules | Your Cursor settings. Global to you, across every project |
| Team rules | Managed in the dashboard. Team and Enterprise plans |
AGENTS.md | Repository root. A simpler alternative to .cursor/rules |
The extension matters: project rules must be .mdc, not .md. A .md file sitting in .cursor/rules/ is not a rule, it is a document nobody reads. /create-rule in chat creates one correctly, which is the easiest way to avoid the mistake.
The frontmatter table that decides everything
This is the part that is worth the whole post. A .mdc rule has three frontmatter fields, alwaysApply, description and globs, and the combination of which ones you set decides whether the rule is ever loaded. Get it wrong and the rule is syntactically fine, committed, visible in the sidebar, and silently inert.
| Frontmatter | When the rule is included |
|---|---|
alwaysApply: true | Every session. globs and description are ignored entirely |
alwaysApply: false plus globs | Automatically, when a file matching the pattern is in context |
alwaysApply: false plus description, no globs | When the agent reads the description and judges the rule relevant |
Neither globs nor description | Only when you @-mention it in chat |
Read the last row twice. A rule with no description and no globs and alwaysApply unset is a manual rule. It will never fire on its own. This is the single most common reason a Cursor rule appears to do nothing.
The third row is worth understanding too, because it moves the decision to the model. Your description is not a comment. It is the text the agent reads when deciding whether to pull the rule in, so it should describe when the rule applies, not what the rule contains. "Conventions for backend RPC services" is a usable trigger. "Rules" is not.
Globs, precisely
Patterns are comma-separated and behave the way they do elsewhere, but the distinction between one star and two is where people lose rules:
| Pattern | Matches |
|---|---|
*.ts | .ts files in the repository root, and nowhere else |
**/*.ts | .ts files in any directory |
src/** | Everything anywhere under src/ |
src/**/*.tsx | .tsx files anywhere under src/ |
docs/**/*.md, docs/**/*.mdx | Two patterns, comma-separated |
tailwind.config.* | Any extension of that filename |
A rule scoped to *.tsx in a repository where every component lives in src/components/ matches nothing. That rule is not wrong, it is just never in the room, and no error tells you so.
One subject per file, under 500 lines
Cursor's documented guidance is to keep a rule under 500 lines and split larger ones into composable rules. The reason is the same reason Claude Code's docs name a 200-line target for CLAUDE.md: a rule is loaded into the context window, and a long one both costs more and is followed less closely.
The structural advantage of .cursor/rules over a single file is exactly this. A 600-line .cursorrules had to be all-or-nothing. Four scoped rule files of 80 lines each load only where they are relevant, which means the agent editing a React component is not also carrying your database migration policy.
.cursor/rules/
project.mdc # alwaysApply: true. Layout, commands, hard rules.
components.mdc # globs: src/components/**/*.tsx
api.mdc # globs: src/app/api/**/route.ts
migrations.mdc # manual. @-mentioned when writing one.One more documented habit that keeps rules short and stops them rotting: reference a file rather than pasting its contents. @migration-template.sql in a rule points the agent at the canonical example, and the example stays correct on its own when the code changes. A pasted copy starts lying the first time someone edits the original.
What not to put in a rule
Cursor's docs are unusually direct about this, and the four things they tell you to leave out are the four things most .cursorrules files are made of:
- An entire style guide. Use a linter. The agent already knows common style conventions, and a formatter enforces them whether the rule loaded or not.
- Every possible command. It knows what
npm,gitandpytestare. Document the command that is specific to your repository, not the one in every tutorial. - Instructions for edge cases. Rules should cover the patterns you hit often. A rule for the thing that happens twice a year is context tax on every session in between.
- Anything already visible in the codebase. Point at the canonical example instead of copying it.
The advice they give for getting started is to start with nothing and add a rule only when you notice the agent making the same mistake repeatedly. That is the opposite of how a copied 300-line file gets adopted, and it produces a much better set of rules, because every line in it exists because something went wrong once.
Three rules worth committing
Concrete, scoped, and each line checkable against a diff. The always-applied one first, kept deliberately short because it is paid for on every request:
---
alwaysApply: true
---
- Next.js App Router, TypeScript strict, Tailwind. Node 20.
- Server components by default. Add "use client" only for state or events.
- Route handlers live in `src/app/api/**/route.ts`. No logic in them.
- Never edit `src/generated/`. It comes from `npm run codegen`.
- Before saying a change is done, run `npm run typecheck` and `npm test`.Then one scoped by path, which loads only when the agent has a component open:
---
globs: src/components/**/*.tsx
alwaysApply: false
---
- Named exports, never `export default`.
- Keep components under 200 lines. Extract a subcomponent into a sibling file
rather than growing past that.
- Styling is Tailwind utilities in the markup. No styled-components, no CSS
modules, no inline `style` objects except for computed values.
- Props interfaces are declared inline above the component, not exported,
unless another file imports them.
- Prefer composition over prop drilling. Pass children or a render prop rather
than threading data through three layers.And one manual rule, for a job that is rare enough that loading it every session would be waste but dangerous enough that you want it in front of you when you do it:
---
alwaysApply: false
---
- Every migration has `up` and `down`. It must be fully reversible.
- Never alter a column type in place. Add the new column, backfill it, drop the
old one in a separate migration.
- Additive first, destructive in a later deploy. The two must never ship
together.
- Structure and naming: @migration-template.sqlAGENTS.md works in Cursor too
Cursor lists AGENTS.md as one of its four rule types, described as a simpler alternative to .cursor/rules. If your team uses more than one agent, that is worth knowing before you invest in .mdc frontmatter: one markdown file at the root is read by Cursor, by OpenAI Codex, by Claude Code, and by most of the others.
The trade is scoping. AGENTS.md scopes by directory, so a monorepo puts one in each package and the nearest file wins. .cursor/rules scopes by glob and by model judgement, which is finer grained and only Cursor understands it. A reasonable split is AGENTS.md for everything true about the project and .cursor/rules for the Cursor-specific mechanics you cannot express in it.
The resolution rules differ between tools, and they have traps. Claude Code's are covered in AGENTS.md vs CLAUDE.md, including the one where adding a private notes file silently turns AGENTS.md off.
The failure is always the same
Across all three of these formats, a rule that does nothing fails in one of exactly two ways. Either it never loaded, which is a frontmatter or glob or resolution problem, or it loaded and was too vague to act on.
The first is diagnosable in a minute: check the frontmatter table above, check the glob, open the rules list and look at the status. The second is a writing problem, and the test is the same one that applies to any prompt. Read the line and ask what you would point at in a pull request to prove it was broken. "Write clean code" fails that test. "Components under 200 lines" passes it.
That test is most of what the rubric behind the scorer on this site is checking for, applied to task prompts rather than rule files. The lessons on constraints and specificity are five minutes each and are the same edit you are making here. The CLAUDE.md post has a table of the swap, line by line.