Skip to content

A Cursor rules library is a set of .mdc files in .cursor/rules/ that gives Cursor’s Agent a team’s conventions and step-by-step templates, each meant to load only when its frontmatter says so. The library pays off only when it is small, scoped, and checked in CI, because a rule that never loads or contradicts the code fails silently.

This page is for the developer or tech lead whose repository already has a few project rules and now wants them to hold up for a whole team. Six months in, the pattern is familiar: 20 rule files, two of them with no frontmatter, so nobody can say when they load, an “API conventions” rule that still points at a file deleted in March, and Agent output that varies by who prompted it. If you are writing your first rule, start with project rules in Cursor and come back.

  • A decision table for what goes in an always-on rule, a file-scoped rule, a template, a skill, or a linter.
  • Five rule templates you can copy and adapt: core, file-scoped, workflow, manual procedure, and review checklist.
  • Three copy-paste prompts that mine your review history for rules, turn canonical files into a template, and audit the library.
  • A CI script that fails when a rule has no frontmatter, a glob matches nothing, or an @ reference is dead.
  • A probe routine that shows the rules change Agent’s output, checked by commands rather than by reading diffs.

Cursor’s UI labels and rule options change often; confirm them against cursor.com/docs/rules before you train a team.

Which frontmatter decides when a rule loads?

Section titled “Which frontmatter decides when a rule loads?”

A rule’s frontmatter carries three keys: alwaysApply, description, and globs. Cursor’s own plugin scaffold, the create-plugin skill in cursor/plugins, writes rules as .mdc files with description, alwaysApply and an optional globs (checked 2026-10-02), and on 2026-10-12 Cursor’s rules docs confirm the same three frontmatter keys. The table maps the four combinations to the load behaviour Cursor has described for them. That behaviour and the rule-picker labels remain as reported before 2026-10-02 and were not re-checked against cursor.com/docs/rules on 2026-10-12, so read the first and last columns as what to expect, not as a guarantee, and confirm each mode in your version with the probes below.

Expected load behaviouralwaysApplydescriptionglobsPicker label (reported, unconfirmed)
Every Agent sessiontrueoptionalnot neededAlways Apply
When a file matching the pattern is in contextfalseoptionalsetApply to Specific Files
When Agent reads the description and judges it relevantfalseset—Apply Intelligently
Only when someone @-mentions the rule in chatfalse——Apply Manually

Five points shape every decision below:

  • Use .mdc with frontmatter. “Project rules live in .cursor/rules as .mdc files”, and “A plain .md file in .cursor/rules is ignored by the rules system” (Cursor docs, checked 2026-10-12). It is also the format Cursor’s own plugin scaffold writes, with description, globs and alwaysApply in the frontmatter. A rule without frontmatter gives Cursor nothing to scope it by.
  • AGENTS.md and CLAUDE.md load beside .cursor/rules/. “Cursor supports AGENTS.md in the project root and subdirectories”, and nested files “are combined with parent directories, with more specific instructions taking precedence.” “Cursor reads CLAUDE.md files the same way it reads AGENTS.md”, and they “are always applied to every conversation, regardless of any alwaysApply frontmatter setting”, so count a CLAUDE.md in the always-on budget below. Cursor’s help calls a .cursorrules file in the project root “legacy and will be deprecated” and says to migrate it to a project rule set to Always Apply (checked 2026-10-12).
  • Write several globs on one globs line, separated by commas: src/**/*.tsx, src/**/*.css. The CI script below accepts that comma list, a YAML list under an empty globs:, and brace patterns such as src/**/*.{ts,tsx}.
  • Rules are written for Agent. Cursor’s rules documentation calls them “system-level instructions to Agent”, and Cursor’s help says: “Rules only apply to Agent (Chat). They do not apply to Tab completion, Inline Edit, or Bugbot PR reviews.” (checked 2026-10-12). Pull request review belongs to Bugbot, which takes its own guidance (see Bugbot).
  • Several sources can apply at once. Team Rules, project rules and User Rules can all reach one session. When they conflict, Cursor’s rules help page says “When rules conflict, precedence is: Team Rules > Project Rules > User Rules” (read 2026-10-13). Confirm the order in your Cursor version with a probe that gives two rules conflicting instructions.

What belongs in a rule, and what does not?

Section titled “What belongs in a rule, and what does not?”

Most weak libraries come from putting the right instruction in the wrong place. Ask what enforces the instruction best, then choose the cheapest mechanism that does it.

The instruction is…Put it inWhy
Needed on almost every task: stack, commands, boundariesAlways-on rule (alwaysApply: true), under ~40 linesEvery session pays for it in context
True only in one area: API routes, migrations, UI componentsFile-scoped rule (globs)Meant to load only where it matters
A multi-step procedure: “add an endpoint”, “add a feature flag”Description-matched rule that points at canonical filesThe description decides; the body stays short
Rare but risky: a data backfill, a releaseManual rule (no description, no globs)Meant to load only when someone asks for it
A long, repeatable workflow with scriptsA skillRules suit short guidelines; skills carry multi-step procedures and their scripts
Mechanically checkable: import order, any, namingA linter or type ruleThe check runs on every commit, not only when Agent remembers
Something Agent already does by defaultNowhereIt costs context and teaches nothing

The last two rows matter most. Leave style to the linter, and add a rule only after Agent repeats the same mistake. Pruning context files explains why the evidence now favours fewer, sharper instructions.

Short, pointer-style procedures can stay as description-matched rules. Once one needs scripts or grows past ~40 lines, convert it to a skill. Cursor lists /migrate-to-skills among its built-in skills for this (checked 2026-10-12); type / in Agent chat to see it in your version.

Start from evidence, not from a style guide. List the corrections your reviewers repeat; the first prompt after the templates does this from your merged pull requests’ review comments, so every rule answers a real mistake. Then write the templates in this order.

  1. Write one short core rule. Save it as .cursor/rules/core.mdc. It carries only what every task needs: the commands that prove work is done and the boundaries Agent must not cross.

    ---
    description: Commands, done criteria and hard boundaries for every task
    alwaysApply: true
    ---
    Stack: TypeScript (strict), Next.js App Router, Drizzle on PostgreSQL, Vitest.
    Done means all of these pass:
    - pnpm lint
    - pnpm typecheck
    - pnpm test
    Boundaries:
    - Never edit files under src/db/migrations/ that are already merged; add a new migration.
    - Never add a dependency without saying why in the summary.
    - Log with the logger in @src/lib/logger.ts, never console.log.
    Before you report a task as done, follow @.cursor/rules/self-review.mdc.
  2. Scope area conventions to their files. One rule per area, named for the area (here .cursor/rules/api-routes.mdc), pointing at the canonical example instead of copying it.

    ---
    globs: src/app/api/**/*.ts
    alwaysApply: false
    ---
    API route conventions:
    - Validate the request body with a Zod schema from @src/lib/validators/index.ts before any other work.
    - Throw AppError from @src/lib/errors.ts; the route wrapper turns it into the JSON error shape.
    - Every query filters by organizationId. A route without it is a security bug.
    Canonical route: @src/app/api/users/route.ts
  3. Turn repeated procedures into workflow templates. Save this one as .cursor/rules/new-endpoint.mdc. The description is what Agent is meant to match against, so write it the way someone would ask for the task.

    ---
    description: Adding a new API endpoint or resource end to end (schema, route, service, tests)
    alwaysApply: false
    ---
    When adding an endpoint:
    1. Schema: add the Drizzle table or column and a new migration in src/db/migrations/.
    2. Validation: add the Zod schema next to the others in src/lib/validators/.
    3. Service: business logic in src/lib/services/, no HTTP types in it.
    4. Route: follow @src/app/api/users/route.ts exactly; copy its structure, not its logic.
    5. Tests: one success path and one AppError path per handler, next to the route.
    Finish by running the commands under "Done means" in the core rule and reporting their output.
  4. Keep risky procedures manual. Save the backfill checklist as .cursor/rules/data-backfill.mdc so it is meant to load only when someone types @data-backfill.

    ---
    alwaysApply: false
    ---
    Data backfill procedure:
    - Write the backfill as a script in scripts/backfills/ with a --dry-run flag that prints row counts.
    - Batch writes in chunks of 1,000 and make the script safe to re-run.
    - Never change a column type in place: add a new column, backfill, switch reads, then drop.
    - Stop after the dry run and show its output; a human runs the real backfill.
  5. Close every task with a self-review. Save the checklist Agent applies to its own diff as .cursor/rules/self-review.mdc. Its description is there so Agent can pick it up at hand-back, and the last line of the core rule names it as well. Do not assume that @ line loads the file: the self-review probe below shows whether the checklist ran, because only this rule asks for the CHANGED FILES: block. Review of the pull request itself is Bugbot’s job; see Bugbot.

    ---
    description: Final self-review before reporting a task as done
    alwaysApply: false
    ---
    Before you say the task is done:
    - Run the commands under "Done means" in the core rule and paste the last lines of each.
    - Write a line that says exactly CHANGED FILES: and under it every file you created, changed or deleted, one repository-relative path per line with no bullets, then one blank line. Then give one line per file on why.
    - Name any test you skipped, weakened or deleted. The answer should be none.
    - Name every new dependency, environment variable or migration.

Commit every file under .cursor/rules/ so the team reviews rule changes like code.

How do you prove the rules work without reading every diff?

Section titled “How do you prove the rules work without reading every diff?”

A rule is an untested instruction until something checks it. Use two layers: a static check that the library is loadable, and behaviour probes that show Agent follows it.

Save this as scripts/check-cursor-rules.mjs and run node scripts/check-cursor-rules.mjs in the same CI job as your linter. It needs Node 22 or later for fs.globSync.

// Fails CI when the .cursor/rules library drifts. Node 22 or later.
import { existsSync, globSync, readFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
const MAX_LINES = 200; // keep each rule short; tune to your library
const ALWAYS_BUDGET = 120; // lines loaded into every Agent session
const errors = [];
const warnings = [];
let alwaysLines = 0;
// Accepts `globs: a, b`, `globs: ["a", "b"]`, brace globs such as
// `src/**/*.{ts,tsx}`, an empty `globs:`, and a YAML list under `globs:`.
function readGlobs(fm) {
const line = fm.match(/^globs:[ \t]*(.*)$/m);
if (!line) return [];
const value = line[1].trim();
let items;
if (value) {
items = value.replace(/^\[|\]$/g, '').split(/,(?![^{]*\})/);
} else {
items = [];
for (const next of fm.slice(line.index + line[0].length).split(/\r?\n/).slice(1)) {
const item = next.match(/^\s*-\s*(.+)$/);
if (!item) break;
items.push(item[1]);
}
}
return items.map((g) => g.trim().replace(/^["']|["']$/g, '')).filter(Boolean);
}
for (const file of globSync('.cursor/rules/**/*.{md,mdc}')) {
const text = readFileSync(file, 'utf8');
const fm = text.match(/^---\r?\n([\s\S]*?)\r?\n---/)?.[1];
if (fm === undefined) {
if (file.endsWith('.md')) {
warnings.push(`${file}: no frontmatter, so it cannot be scoped; prefer .mdc with description, globs or alwaysApply`);
} else {
errors.push(`${file}: no frontmatter`);
}
continue;
}
const lines = text.split('\n').length;
if (lines > MAX_LINES) errors.push(`${file}: ${lines} lines (max ${MAX_LINES})`);
if (/^alwaysApply:\s*true\b/m.test(fm)) alwaysLines += lines;
for (const g of readGlobs(fm)) {
if (globSync(g, { exclude: (p) => p.includes('node_modules') }).length === 0) {
errors.push(`${file}: glob "${g}" matches no file`);
}
}
for (const [, ref] of text.matchAll(/(?:^|\s)@([\w./-]+\.\w+)/gm)) {
if (!existsSync(ref) && !existsSync(join(dirname(file), ref))) {
errors.push(`${file}: @${ref} does not exist`);
}
}
}
if (alwaysLines > ALWAYS_BUDGET) {
errors.push(`always-apply rules total ${alwaysLines} lines (budget ${ALWAYS_BUDGET})`);
}
for (const w of warnings) console.warn(`warning: ${w}`);
console.log(errors.length ? errors.join('\n') : 'cursor rules: OK');
process.exit(errors.length ? 1 : 0);

Both limits are starting points: the 200-line ceiling per rule and the 120-line always-on budget should be tuned against your own token use. Add .cursor/rules/ to CODEOWNERS so one named owner signs off on every rule change.

Probe behaviour with checks, not by reading

Section titled “Probe behaviour with checks, not by reading”

Keep a short docs/rule-probes.md in the repository. Each row is a task you give Agent in a fresh chat and a command whose result decides whether the rules worked.

Probe taskRule it exercisesDeterministic check
“Add GET /api/invoices/[id]”API routes and endpoint workflowpnpm test src/app/api/invoices passes and rg -c organizationId src/app/api/invoices is at least 1
“Log the retry count in the payment service”Core rulerg -n "console\.log" src/lib/services/payment* finds nothing
“@data-backfill: fill invoices.currency”Manual procedurerg -l -- --dry-run scripts/backfills/ returns the new script, and git diff --stat shows no migration or data change was applied
Any of the tasks aboveSelf-review rule loadedThe command below exits 0

For the self-review row, save Agent’s final message as /tmp/summary.txt. The command compares the CHANGED FILES: block with what git sees, including new and deleted files. A missing block fails it too, which means the rule never loaded.

Terminal window
diff <(sed -n '/^CHANGED FILES:$/,/^$/p' /tmp/summary.txt | sed '1d;/^$/d' | sort -u) \
<(git ls-files --modified --deleted --others --exclude-standard | sort -u) \
&& echo "self-review: file list matches"

The checks are commands, but someone still has to start each task. To run the probes unattended, script them with the Cursor SDK: the @cursor/sdk package calls Cursor’s agent from your own code (Node 22.13 or later), so a scheduled job can give Agent each probe task in a fresh run on a clean checkout and then execute the check column. The SDK lets you choose which settings layers apply (project, user, team, plugins), so make sure the run loads the project layer that holds .cursor/rules/. Run the probe job in a disposable sandbox or a cloud agent, with no production secrets in its environment and a read-only repository token.

Run the probes after any rule change and before you promote a rule to Team Rules. A failed probe means the rule is unclear, never loaded, or contradicted by another rule; the next section tells you which. For the same idea across Claude Code, Codex and Cursor, see proving every agent loaded the shared rules.

How do you share rules across repositories and the team?

Section titled “How do you share rules across repositories and the team?”

Pick by who must follow the rule and whether they may opt out.

  • One repository: project rules in .cursor/rules/, committed and owned through CODEOWNERS.
  • Every repository, set by admins: Team Rules, managed from the Cursor dashboard. They apply across the team’s repositories and win over a project rule when the two conflict (Cursor’s rules help page, read 2026-10-13), so promote a rule only after it passes its probes in one repository. Since Cursor 3.21, a team rule with file patterns applies only to the files that match them (checked 2026-10-12).
  • A reusable bundle: to reuse a set of rules across repositories, package them in a plugin with a rules/ folder of .mdc files. Cursor’s docs describe plugins as packaging “rules, skills, agents, commands, MCP servers, and hooks into distributable bundles”. Cursor’s own cursor/plugins repository ships rules this way (every rule has description and alwaysApply), and its create-plugin scaffold lists globs as optional.
  • Several agents on one repository: keep one source and generate Cursor’s files from it; see one rules source for every agent.

Team-wide rollout, ownership and review norms are covered in team collaboration in Cursor.

A rule never loads, or loads everywhere. The frontmatter is missing or broken: no --- fences, a misspelled field, or a plain .md file, which Cursor’s rules system ignores (checked 2026-10-12). Recovery: save it as .mdc with description, globs or alwaysApply, then run the CI script; it flags a rule with no frontmatter.

A description-matched rule is skipped for the task it was written for. Its description names a topic (“API stuff”) rather than a task. Recovery: rewrite the description as the request a developer would type, then rerun the probe. If it still misses, scope it with globs instead.

A glob rule never fires. The pattern points at a path that moved, or it is broader or narrower than you think. Recovery: prefer explicit patterns such as src/**/*.ts, and let the CI script confirm that each glob matches files.

Agent follows a rule that contradicts the code. The rule describes last year’s pattern while the canonical file changed. Recovery: replace pasted code with @ references so the rule follows the file, and delete rules the audit prompt marks as contradicted.

Context fills up before the task starts. Always-on rules grew one line at a time. Recovery: move area-specific lines to glob rules, move procedures to skills, and hold the always-on budget in CI.

A Team Rule overrides a project rule. When rules conflict, Team Rules win (Cursor’s rules help page, read 2026-10-13), so a generic organization rule can beat a precise repository rule. Recovery: run the conflicting-instructions probe to confirm the order in your version, keep Team Rules to policy (security, data handling), and leave code conventions to project rules.

The team expects rules in Tab or in pull request review. Cursor’s help says: “Rules only apply to Agent (Chat). They do not apply to Tab completion, Inline Edit, or Bugbot PR reviews.” (checked 2026-10-12). Bugbot takes its own guidance. Recovery: give Bugbot its own review guidance (see Bugbot) and enforce style with the linter.