Cursor Rules: The Four Types and When Each Fires
A rule that never fires is worse than no rule, because the team believes it is covered. Cursor decides whether a rule applies from three frontmatter fields, and one combination of them produces a rule that only runs if somebody remembers to mention it by hand. Teams write that combination by accident more often than any other.
Where rules live
Project rules go in .cursor/rules as .mdc files, committed with the repository so the whole team gets the same behaviour and any change to it arrives through review. Subdirectories work, so .cursor/rules/frontend/components.mdc is a valid path and a good habit once you have more than a handful.
One detail causes more confusion than everything else combined: plain .md files in that directory are ignored. A rule saved as spec-first.md is not a rule. It is a file. Nothing warns you. If you want plain markdown, the supported route is AGENTS.md, which Cursor also reads.
User rules are a separate mechanism, global to your Cursor environment rather than to a project. They are the wrong place for anything a colleague also needs, which is most things. If a rule matters to the codebase, it belongs in the codebase.
Three fields, four types
Cursor derives the rule type from the frontmatter. There is no type: field — the combination is the type.
| Type | Fires when | Frontmatter |
|---|---|---|
| Always apply | Every chat session | alwaysApply: true |
| Apply intelligently | The agent judges it relevant | description present, no globs |
| Apply to specific files | A matching file is in play | globs set, alwaysApply: false |
| Apply manually | Only when @-mentioned in chat | alwaysApply: false, no description, no globs |
The highlighted row is the one to watch. It is what you get by writing a rule body, saving the file, and leaving the frontmatter mostly empty — which is exactly what someone does when they are in a hurry and assume rules apply by default. The rule is well written, committed, reviewed, and inert.
Glob syntax is standard: * for one segment, ** for recursive, and comma-separated patterns for several at once, as in docs/**/*.md, docs/**/*.mdx.
Choosing the trigger for a spec-first rule
The type decides whether a rule is load-bearing or decorative, so pick it from the rule's purpose rather than from habit.
| Rule purpose | Type to use | Why |
|---|---|---|
| Non-negotiables: never edit generated output, no new dependency without approval | Always apply | A boundary that only holds sometimes is not a boundary |
| Contract discipline for API surfaces | Specific files, globs: openapi/**, src/api/** | Attaches exactly when the risky files are open, and stays silent otherwise |
| Migration and schema safety | Specific files, globs: migrations/** | The same reason, with higher stakes |
| The spec-drafting procedure | Apply intelligently, with a description | You want it when a request is underspecified, which no glob can detect |
| Anything with side effects: release steps, data backfills | Apply manually | Deliberate invocation is the point; automatic firing is the hazard |
For the "apply intelligently" case, the description is doing real work: it is what the agent reads to decide relevance. Write it as trigger conditions, not as a summary. "Use when a request names a feature or fix but leaves scope, failure paths, or acceptance criteria undecided" gets selected. "Spec writing guidelines" does not.
A working set of three
Most teams need fewer rules than they think. Three files cover the common ground.
.cursor/rules/boundaries.mdc
---
alwaysApply: true
---
- Generated output is off limits. Edit the generator, not its files.
- No new dependency for a problem under ~20 lines.
- Migrations, auth, and billing need an approved spec before edits.
.cursor/rules/api-contracts.mdc
---
description: Contract rules for API surfaces
globs: openapi/**, src/api/**
alwaysApply: false
---
- A response shape change is a contract change: version it or keep
the old shape working.
- Every new error path needs a documented code and a test.
.cursor/rules/spec-first.mdc
---
description: Use when a request names a feature, bug, or API change but
leaves scope, failure paths, or acceptance criteria undecided. Produces
the spec before any implementation.
alwaysApply: false
---
Write the spec first. Do not propose an implementation in this turn.
Required: goal / non-goals / behaviour / failure paths /
acceptance criteria (AC-1..n) / evidence per criterion / rollback.
Leave unknowns as {to-be-filled}. Never invent a requirement.
Note what is not in boundaries.mdc: formatting preferences, naming style, import order. Anything a linter or formatter already enforces is wasted space in an always-applied rule, and always-applied rules are the ones you pay for on every session. Keep individual rules under about 500 lines and split rather than grow them.
Two failure modes
Everything set to alwaysApply. It feels safe and it is the most common mistake. Every session then carries every rule, the useful instructions compete with the trivial ones for attention, and the set becomes too expensive to extend — so the next genuinely important rule does not get written. Reserve always-apply for boundaries you would enforce in review anyway.
Rules that describe intentions instead of behaviour. "Write clean, maintainable code" cannot be followed or violated in a checkable way. "Every new endpoint needs a test named after the behaviour it proves" can. This is the same standard a good acceptance criterion meets, and for the same reason: an instruction that cannot be checked will not change what gets produced. Our acceptance criteria guide covers the phrasing in more depth.
A rule that did not fire: the diagnosis order
Here is the shape of the incident, because it repeats. A team adds a rule requiring an approved spec before any migration edit. Weeks later an agent writes a migration during a routine change, nobody notices until review, and the first instinct is to rewrite the rule in stronger language. That is almost always the wrong move: the rule was fine, and it never reached the model.
Check three things, in this order, before touching a single word of the body.
| Check | What you are looking for | Symptom if this is the cause |
|---|---|---|
| 1. The extension | Is it .mdc? A .md file in .cursor/rules is ignored outright | The rule has never once had an effect, in any file, since it was added |
| 2. The type | Does the frontmatter select a trigger at all, or did it land on manual? | Same as above, and the rule works when @-mentioned by hand |
| 3. The glob depth | * matches one segment; ** recurses | It fires in some directories and not others |
The third one is the subtle case and worth stating plainly. globs: migrations/* matches migrations/001_init.sql and does not match migrations/2026/09/add_retry_column.sql, because * stops at a path segment. A team that organises migrations into dated folders has a rule that quietly covers only the flat files nobody writes any more. Write migrations/** unless you specifically mean one level.
The general lesson is the one worth carrying to any instruction mechanism: an instruction that never loads and an instruction the model chose to ignore look identical from the outside. They have completely different fixes, so establish which one you have before editing anything. Open a chat with a matching file and confirm the rule is attached — five seconds of verification beats a week of assuming.
Rules, AGENTS.md, and the spec
Cursor reads AGENTS.md as well, in the project root or nested in subdirectories, with the more specific file taking precedence. That leaves a real choice about where a given fact belongs, and the answer follows from portability and triggering.
| Put it in | When |
|---|---|
AGENTS.md | Repository-wide context every agent tool should see. Portable across twenty-plus tools, so this is the default for stack, commands, and boundaries |
An .mdc rule | You need Cursor's triggering: attach only for migrations/**, or let the agent select a procedure by description |
| The spec for one change | Goal, non-goals, acceptance criteria, evidence. Anything that would need rewording for the next feature |
State each fact in one place. A boundary written in both AGENTS.md and an always-applied rule will drift apart, and nobody will be able to say which version the agent actually used. The AGENTS.md durability test is the tool for deciding the third row: if a line would need different wording for the next feature, it is not repository context at all.
What to check after a week
Rules are cheap to write and easy to leave broken, so verify them the way you would verify anything else. Open a chat in a file the globs should match and confirm the rule is actually attached rather than assuming. Grep the directory for .md files that should have been .mdc. Count your always-applied rules — more than a few is a signal that the set needs re-typing rather than extending. And when an agent does something a rule forbade, treat it as a triggering bug first: check which type the rule is before rewriting its wording, because a well-worded rule that never fires reads exactly like a rule the model ignored.
If you are still deciding what the spec itself should contain, the Cursor spec template gives you the fields, and the spec packet generator produces them from a plain description.
References
- Cursor documentation: Rules — the
.mdcformat, the three frontmatter fields, the four rule types, glob syntax, nested rules, project versus user rules, and the 500-line guidance. - AGENTS.md — the portable alternative Cursor also reads, root or nested.
Field names and rule types verified against the linked Cursor documentation on 2026-09-08. Cursor changes quickly; check the source before relying on a specific field.