I am a...
Learn more
How it worksPricingFAQ
Account
May 8, 2026 · 10 min read · Cadence Editorial

Cursor rules: how to configure them right

cursor rules guide — Cursor rules: how to configure them right
Photo by [Nemuel Sereti](https://www.pexels.com/@nemuel) on [Pexels](https://www.pexels.com/photo/computer-program-on-the-monitor-6424585/)

Cursor rules: how to configure them right

Cursor rules are version-controlled instructions that steer Cursor's AI inside your repo. The modern setup lives in .cursor/rules/ as one .mdc file per concern, each tagged with an activation mode: Always Apply, Auto Attached, Agent Requested, or Manual. Configure them right and the AI stops re-inventing your conventions every session.

If you migrated to Cursor in 2023 or 2024, you probably have a single .cursorrules file at the repo root. That format still works, but it is the legacy path. Cursor 0.45 introduced a per-feature rules system that is more precise, more debuggable, and dramatically cheaper on tokens. This post is the deep dive on the new system, not a recap of the old one. If you want a broader Cursor walkthrough first, our Cursor for developers guide covers the editor end-to-end.

The modern Cursor rules system, in one paragraph

Project rules now live in .cursor/rules/ as individual .mdc files. Each file has YAML frontmatter (description, globs, alwaysApply) plus a markdown body. The frontmatter decides when the rule fires; the body is the prose the model reads. Splitting rules per concern means a Tailwind rule only loads when you touch a .tsx file, a Drizzle rule only loads when you touch the schema, and your always-on context stays small. Cursor still reads .cursorrules at the repo root if present, but the per-file system gives you scoping you cannot get from a single monolith. See Cursor's official rules docs for the canonical reference.

The four activation modes, and when to use which

Every .mdc rule sits in exactly one mode, decided by which frontmatter fields you set:

ModeTriggerBest forToken costMain risk
Always ApplyEvery chatStack baseline, tone, top-level conventionsHigh (every request)Token tax, over-steering
Auto AttachedGlob match in contextPer-feature conventions (Drizzle, Tailwind, server actions)Medium (when files in context)Globs too broad or too narrow
Agent RequestedAgent reads description and decidesTool-specific procedures the agent needs occasionallyLowAgent skips when description is vague
ManualExplicit @rule-nameHeavy, rare workflows (release prep, migration runbooks)Zero unless invokedYou forget it exists

The rule of thumb: pick the narrowest mode that always fires when you need it. Always Apply is the most expensive (every prompt pays for it) and the least precise. Auto Attached is the workhorse. Agent Requested works for procedures the model can detect from the user message. Manual is for heavyweight rules you opt into.

A 500-word always-apply rule costs roughly 650 tokens on every single Cursor request. Multiply by the agent's planning loop and you can blow past 5,000 tokens before the model has read a line of your code. Auto-attached rules only pay that cost when you are actually in a file that needs them.

Anatomy of a .mdc rule file

Here is the shape:

---
description: "Drizzle schema conventions for this repo"
globs: ["db/schema/**/*.ts"]
alwaysApply: false
---

When editing files in db/schema:

- Use `pgTable` with snake_case column names. The TS field stays camelCase via the second arg.
- Every table gets `id` (uuid, primaryKey, defaultRandom) and `createdAt` / `updatedAt` (timestamp with timezone, default now).
- Foreign keys must declare `references(() => other.id, { onDelete: "cascade" })` explicitly. No silent ON DELETE NO ACTION.
- Enums go in db/schema/enums.ts as `pgEnum`. Never inline.
- Run `pnpm db:generate` after any schema change. Mention this to the user if they did not already.

Reference the canonical pattern: @db/schema/users.ts

Three things make that example work. First, the body is prescriptive (do this, not this) instead of descriptive (Drizzle is an ORM that...). The model already knows what Drizzle is. Second, the rule references a real file with @db/schema/users.ts, so the model reads your actual conventions rather than guessing. Third, the glob is tight: it only fires when you touch the schema directory.

Real .mdc rules for a Next.js / Drizzle / Tailwind / Zod stack

Here is the shape of a working .cursor/rules/ directory for a typical 2026 Next.js stack:

.cursor/rules/
  always-apply.mdc          # 150 words, every chat
  drizzle-schema.mdc        # auto, db/schema/**
  server-actions.mdc        # auto, app/**/actions.ts
  tailwind-styling.mdc      # auto, **/*.tsx
  zod-validation.mdc        # agent-requested
  release-prep.mdc          # manual, @release-prep

A few notes on each. The always-apply.mdc file is the only one that loads on every chat, so it stays under 200 words: "This is a Next.js 15 App Router app. Server components by default. Drizzle for the DB, Tailwind v4 for styling, Zod for input validation, server actions for mutations. We use pnpm. Tests live next to source as *.test.ts. Lint with pnpm lint, format with Prettier. Voice: short sentences, no em dashes."

drizzle-schema.mdc is auto-attached to db/schema/**/*.ts. It only fires when the user is editing the schema. server-actions.mdc is auto-attached to app/**/actions.ts and tells the model to validate inputs with Zod, return discriminated { ok: true, data } | { ok: false, error } shapes, and never call the DB without a session check.

tailwind-styling.mdc is the controversial one. Auto-attaching to **/*.tsx means it fires on almost every component edit, which is what you want, but you need to keep the rule tight (200 words is plenty) or you reintroduce the token tax. Mine: prefer Tailwind utility classes over inline styles, use cn() from @/lib/utils for conditional classes, never !important, design tokens come from the tailwind.config.ts theme.

zod-validation.mdc is agent-requested with a description like "Use when defining input schemas for forms, API routes, or server actions." The agent loads it when relevant. Less precise than auto-attaching, but you avoid pulling Zod conventions into chats about CSS.

release-prep.mdc is manual: @release-prep triggers a long checklist (bump version, regenerate changelog, tag commit, update Vercel env vars, smoke-test staging). Heavy, rare, opt-in.

How rules compose with CLAUDE.md and other agents

If you only use Cursor, skip this section. If you also run Claude Code or Continue, this is where most teams quietly create chaos. Cursor reads .cursor/rules/*.mdc and (legacy) .cursorrules. Claude Code reads CLAUDE.md. Several agents now read the cross-tool AGENTS.md standard. Each tool happily ignores the others.

The drift problem: you update a Cursor rule, forget the CLAUDE.md, and now your two AI tools disagree on whether server actions return Result types. Three weeks later you cannot remember which one is right. The senior pattern that maps cleanly to prompt engineering for senior software engineers: treat the project-wide standards as a single source of truth, then mirror them.

A practical setup. Keep your project-wide conventions (stack, voice, Result shapes, file naming) in a single canonical file like docs/AI-CONVENTIONS.md. Reference it from CLAUDE.md with @docs/AI-CONVENTIONS.md and from your always-apply.mdc with the same @-reference. Tool-specific behavior (Cursor agent panel quirks, Claude Code's plan mode, slash commands) lives only in the file that tool actually reads. Now there is one place to update conventions, and each tool gets the slice it needs.

Rules vs prompts vs system messages

A confusion worth unwinding before you write your first rule. There are three layers:

  1. The system message is what the model is. Cursor sets this. You do not edit it.
  2. Cursor rules are what your repo expects. Long-lived, version-controlled, opinionated.
  3. The prompt is what you want done right now. Ephemeral, in the chat box.

The most common mistake is shoving prompt-level instructions into rules. "Refactor getUserById to use the new auth helper" is a prompt, not a rule. "All auth helpers live in lib/auth/ and return Promise<Result<User, AuthError>>" is a rule. The first is a one-shot task; the second is a standing convention.

The second most common mistake is the inverse: putting rules into prompts. If you find yourself pasting "remember, we use Zod for input validation" into chat for the tenth time, that is a rule fighting to be born. Move it to a .mdc file.

How to test that a rule is actually loading

The biggest gap in most Cursor rules workflows: people ship rules and never verify they fire. Then they wonder why the agent ignores them. Three checks:

Open a file that matches the glob. If the rule is auto-attached to db/schema/**, open db/schema/users.ts, then start a fresh chat. The Cursor Composer panel shows attached rules near the top. If your rule is not listed, the glob is wrong.

Ask the agent directly. In a chat, type "list the active project rules and which mode they are in." Cursor's agent will name them. If your rule is missing, it did not load.

Run a wrong-on-purpose request. Best test of rule effectiveness. If your drizzle-schema.mdc says foreign keys must declare onDelete explicitly, ask the agent to add a foreign key without onDelete. If the rule is firing, the agent should add it (or push back). If the agent silently produces a no-action FK, the rule did not bite.

Iterate. Tighten the glob, sharpen the description, shorten the prose. A rule that does not fire is worse than no rule, because you assume coverage you do not have.

Anti-patterns to avoid

A short list of mistakes I see in real codebases:

  • Over-broad globs. globs: ["**/*"] on a non-trivial rule reintroduces the always-apply token tax without the explicit opt-in.
  • Always-apply rules over 500 words. The model starts ignoring middle paragraphs (the lost-in-the-middle effect). Split into auto-attached rules.
  • Rules that fight the linter. If your ESLint config bans default exports and your Cursor rule encourages them, the rule loses every time. Mirror the linter; do not fight it. Same logic as AI-powered debugging workflows in 2026: the model should respect your toolchain, not work around it.
  • Vague agent-requested descriptions. "Use when relevant" is not a description. "Use when generating Zod schemas for form inputs or API request bodies" is.
  • Rules as documentation. A rule that explains what Drizzle is wastes tokens. The model knows. A rule that prescribes how this repo uses Drizzle earns its place.

What to do next

If you have not migrated off .cursorrules yet, the win is concrete: split that monolith into 3 to 5 auto-attached rules and a small always-apply.mdc. You will cut per-request token cost by 40 to 60 percent and the agent will be sharper on the slices it does load. Then commit .cursor/rules/ to git so your team gets the same behavior on day one.

If you are hiring engineers and want to know whether they actually use this stuff: ask them to walk you through their .cursor/rules/ setup on a recent project. AI-native engineers will have one. Engineers still treating Cursor as autocomplete will not. On Cadence, every engineer is AI-native by default. The voice interview specifically scores Cursor and Claude Code fluency, including how candidates structure rules and prompt-as-spec discipline, before they unlock bookings. There is no non-AI-native option on the platform. Tiers run from junior at $500/week through mid at $1,000, senior at $1,500, and lead at $2,000, all weekly billing with a 48-hour free trial.

Steps

  1. Install or upgrade Cursor. You need 0.45 or later for .cursor/rules. Check Cursor > About or run cursor --version.
  2. Create the rules directory. mkdir -p .cursor/rules. Add it to .gitignore's allow list if you have a global ignore.
  3. Write always-apply.mdc. Keep it under 200 words. Cover stack, conventions, and tone. Set alwaysApply: true in frontmatter.
  4. Add one auto-attached rule. Pick your highest-friction file pattern (schema, components, server actions). Set globs: [...], leave alwaysApply: false.
  5. Reference real files with @file. Inside the rule body, point to a canonical example with @db/schema/users.ts. The model reads it.
  6. Test the rule fires. Open a matching file, start a chat, ask the agent to list active rules. Run a wrong-on-purpose request to confirm enforcement.
  7. Iterate weekly. Each Friday, review which rules fired and which did not. Tighten globs, shrink prose, retire dead rules.

Not sure whether your next feature is a build, a buy, or a rule-the-agent-can-handle? Run it through Cadence's Build / Buy / Book decider. 60 seconds, opinionated answer, no signup.

FAQ

Is .cursorrules still supported?

Yes, but it is the legacy format. Cursor still reads it at the repo root, and you will not lose your rules overnight. New projects should start with .cursor/rules/*.mdc because you give up per-feature scoping and the four activation modes if you stay on the single-file format.

How many rules should a project have?

Most production repos converge at 5 to 10 rule files. One always-apply foundation, three to five auto-attached per-feature rules (schema, styling, server actions, tests), one or two agent-requested procedure rules, and an optional manual rule for release prep or migrations. More than 15 and you are usually duplicating prose that belongs in one place.

Can a Cursor rule conflict with CLAUDE.md?

Yes if you let them drift. The fix is a single source of truth. Keep project-wide conventions in a canonical doc (we use docs/AI-CONVENTIONS.md), reference it from both CLAUDE.md and your always-apply.mdc with @-references, and write tool-specific behavior only in the file the relevant tool reads.

What's the difference between a Cursor rule and a system prompt?

A system prompt defines what the model is (assistant persona, capabilities, refusals). Cursor sets this and you do not edit it. A rule is what your repo expects on top of that. Rules ride on top of the system prompt and add project-specific instructions. Prompts (your chat messages) are ephemeral asks on top of both.

Do I commit .cursor/rules to git?

Yes. Rules are shared project intelligence, not user preferences. Commit the directory so your team gets identical AI behavior, code reviewers can see what conventions are encoded, and new engineers get the rules on first clone. User-specific preferences belong in Cursor Settings, not in the repo.

All posts