guides

Cursor Rules: How to Write .cursor/rules Files That Actually Work

Written by Mert Batur
Updated Jul 5, 2026
11 read
Cursor Rules: How to Write .cursor/rules Files That Actually Work

Cursor Rules: How to Write .cursor/rules Files That Actually Work

Every Cursor user hits the same wall. The AI generates code that technically works but ignores your project's conventions, wrong import paths, outdated patterns, components structured nothing like the rest of your codebase. Cursor rules fix that by giving the AI persistent context about how your project works.

What Are Cursor Rules and Why Do They Matter?

Cursor rules are markdown files that act as a permanent system prompt injected before every AI interaction, chat, autocomplete, code generation, all of it. Think of them as onboarding docs for the AI. Instead of correcting the same mistakes every session, you write the instruction once and it sticks.

The old approach was a single .cursorrules file in your project root. That still works, but it's deprecated. The current system uses a .cursor/rules/ directory with individual .mdc (Markdown Cursor) files, each scoped to specific situations. This is a much better setup because you're not cramming every instruction into one giant file, you split rules by concern, and Cursor only loads the ones relevant to what you're doing right now.

If you've worked with context engineering for AI tools, the concept is familiar: better input context produces dramatically better output. Rules are context engineering for your entire development workflow.

Setting Up Your First Rule File

Create the .cursor/rules/ directory at your project root:

bash
mkdir -p .cursor/rules

Each rule is a .mdc file with YAML frontmatter followed by markdown content. Here's the skeleton:

yaml
---
description: "When this rule should apply"
globs: ["src/components/**/*.tsx"]
alwaysApply: false
---

Your instructions go here in plain markdown.

Three frontmatter fields control everything:

FieldTypePurpose
alwaysApplybooleanInclude in every AI request when true
descriptionstringHelps the agent decide if this rule is relevant
globsstring[]File patterns that trigger this rule

You can also create rules through Cursor itself, type /create-rule in chat and describe what you want. But writing them by hand gives you more control.

The Four Rule Types Explained

How a rule activates depends on its frontmatter configuration. There are four modes, and picking the right one matters for your context window budget.

Always Apply

yaml
---
alwaysApply: true
---

Loaded into every single AI request. Use this sparingly, for project-wide fundamentals like your tech stack declaration or critical conventions that apply everywhere. Every always-on rule eats tokens from every interaction, whether relevant or not.

Auto-Attached (Glob-Based)

yaml
---
globs: ["src/api/**/*.ts", "src/routes/**/*.ts"]
alwaysApply: false
---

Activates only when you're editing files that match the glob patterns. This is the workhorse rule type. Your React component conventions load when you're in component files, your API patterns load when you're in route handlers, your test rules load when you're writing tests.

Agent-Requested (Intelligent)

yaml
---
description: "Database migration patterns using Drizzle ORM"
alwaysApply: false
---

No globs, no always-apply, just a description. Cursor's agent reads the description and decides whether the rule is relevant to the current task. If you ask it to write a migration, it pulls in this rule. If you're styling a button, it skips it. This works surprisingly well for rules that don't map neatly to file paths.

Manual

yaml
---
---

No frontmatter fields set (or empty frontmatter). These rules only activate when you explicitly mention them with @rule-name in chat. Good for rarely-used but important instructions, like deployment checklists or refactoring guides you only need occasionally.

Rule TypeWhen It LoadsBest For
Always ApplyEvery requestTech stack, critical conventions
Auto-AttachedMatching file openFramework patterns, file-type rules
Agent-RequestedAgent decidesCross-cutting concerns, workflows
Manual@-mentionedOne-off tasks, checklists

Glob Patterns That Actually Work

Globs determine which files trigger auto-attached rules. Get them wrong and your rules either never fire or fire everywhere. Here's what works:

yaml
# All TypeScript files in src
globs: ["src/**/*.ts", "src/**/*.tsx"]

# Only component files
globs: ["**/components/**/*.tsx"]

# Python files, excluding tests
globs: ["**/*.py", "!**/test_*.py"]

# Multiple specific directories
globs: ["src/api/**", "src/services/**"]

A few gotchas from real usage:

  • src/* only matches one directory level. You almost always want src/**/* for recursive matching.
  • *.js won't match .jsx or .ts files. Be explicit about extensions.
  • Globs must be a YAML list. The brace syntax like {src,lib}/**/*.ts can fail silently, stick with separate list entries.
  • The ! prefix excludes patterns, which is useful for ignoring generated files or legacy code.

Practical Rule Examples

Here's where theory meets reality. These are rules you can drop into a project and immediately see better AI output.

Project-Wide Base Rule (Always Apply)

yaml
---
alwaysApply: true
---

# Project: Acme Dashboard

## Tech Stack
- Next.js 15 (App Router only — no Pages Router)
- TypeScript strict mode
- Tailwind CSS v4
- Drizzle ORM with PostgreSQL
- pnpm for package management

## Critical Conventions
- All components are React Server Components by default
- Use "use client" only when the component needs interactivity
- Import paths use @/ alias mapped to src/
- Error handling: wrap async operations in try/catch, never use .catch()
- No default exports except for pages and layouts

Keep this under 30 lines. It's loaded with every request, so every word costs tokens.

React Component Rule (Auto-Attached)

yaml
---
description: "React component patterns and conventions"
globs: ["src/components/**/*.tsx", "src/app/**/*.tsx"]
alwaysApply: false
---

# React Component Rules

## Structure
Every component file follows this order:
1. Imports
2. Type definitions (Props interface)
3. Component function (named export)
4. Sub-components (if any)

## Patterns

Use named exports, not default:
- YES: `export function Button({ label }: ButtonProps)`
- NO: `export default function Button()`

For data fetching in Server Components:
```tsx
// Fetch directly in the component, no useEffect
export async function UserProfile({ id }: { id: string }) {
  const user = await db.query.users.findFirst({
    where: eq(users.id, id)
  });
  return <div>{user.name}</div>;
}

Anti-Patterns (NEVER do these)

  • No useEffect for data fetching in Server Components
  • No CSS modules — use Tailwind exclusively
  • No barrel exports (index.ts re-exports)
  • No prop drilling beyond 2 levels — use context or composition
text

### Python API Rule (Auto-Attached)

```yaml
---
description: "FastAPI endpoint conventions and patterns"
globs: ["src/api/**/*.py", "src/routes/**/*.py"]
alwaysApply: false
---

# FastAPI Conventions

## Endpoint Structure
- Use APIRouter for route grouping
- Type all request/response models with Pydantic v2
- Dependency injection for database sessions

## Pattern
```python
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession

router = APIRouter(prefix="/users", tags=["users"])

@router.get("/{user_id}", response_model=UserResponse)
async def get_user(
    user_id: int,
    db: AsyncSession = Depends(get_db)
) -> UserResponse:
    user = await db.get(User, user_id)
    if not user:
        raise HTTPException(status_code=404, detail="User not found")
    return UserResponse.model_validate(user)

Error Handling

  • Always use HTTPException, not raw Response objects
  • Log errors with structlog before raising
  • Return consistent error shapes: {"detail": "message"}
text

### Go Service Rule (Auto-Attached)

```yaml
---
description: "Go service patterns and error handling"
globs: ["**/*.go", "!**/*_test.go"]
alwaysApply: false
---

# Go Conventions

## Error Handling
- Always handle errors immediately — no _ for error returns
- Wrap errors with fmt.Errorf("context: %w", err)
- Use sentinel errors for expected failure cases

## Project Layout
- cmd/ for entrypoints
- internal/ for private packages
- pkg/ for public libraries

## Pattern
```go
func (s *UserService) GetByID(ctx context.Context, id string) (*User, error) {
    user, err := s.repo.Find(ctx, id)
    if err != nil {
        if errors.Is(err, ErrNotFound) {
            return nil, fmt.Errorf("user %s: %w", id, ErrNotFound)
        }
        return nil, fmt.Errorf("fetching user %s: %w", id, err)
    }
    return user, nil
}
text

## Managing the Token Tax

Here's something most Cursor guides skip: every rule you write costs tokens. A project with 20 always-on rules might burn **2,000+ tokens per request** just on instructions, before the AI even looks at your code.

That matters because Cursor's chat context is roughly 20,000 tokens in standard mode. If your rules eat 25% of that, you've lost a quarter of the AI's "thinking space" for your actual question. You'll notice worse output quality as rules pile up, especially in longer conversations.

Three principles keep your token budget healthy:

**1. Use auto-attached and agent-requested rules aggressively.** Only your project stack declaration should be always-on. Everything else should load conditionally. That React component rule? It doesn't need to be in context when you're writing SQL migrations.

**2. Write dense, not wordy.** Replace "It is strongly recommended that developers use TypeScript interfaces rather than type aliases when defining public API contracts" with "Prefer `interface` over `type` for public APIs." The AI doesn't need persuasion, it needs instructions.

**3. Apply the Rule of Three.** Only codify a pattern as a rule after the AI gets it wrong three times. If Cursor already handles your naming conventions correctly without a rule, skip the rule. Every unnecessary rule is wasted context.

You can monitor token usage in the status bar at the bottom of Cursor's chat panel. Watch for it approaching 100%, that's your signal to prune.

## Organizing Rules for a Real Project

A production project typically needs 5-8 rule files. Here's a structure that works well:

```text
.cursor/rules/
  base.mdc            # Tech stack, always-apply (< 30 lines)
  components.mdc      # React/Vue patterns, glob to component dirs
  api.mdc             # Backend conventions, glob to API dirs
  database.mdc        # ORM patterns, glob to models/migrations
  testing.mdc         # Test conventions, glob to test files
  deployment.mdc      # CI/CD patterns, manual trigger
  personal.mdc        # Your preferences (gitignored)

Commit everything to version control except personal.mdc. That way your entire team gets the same AI behavior, which is the whole point. As one Cursor forum user puts it, good rules mean "you accept more suggestions as-is, with output matching your conventions on the first try."

If you're working with other AI coding tools alongside Cursor, the concepts transfer directly. Claude Code uses CLAUDE.md, while Codex reads AGENTS.md, GitHub Copilot has instruction files, and Windsurf has its own format, but the underlying principle is identical.

How Rule Precedence Works

When multiple rules apply to the same file, Cursor follows a clear hierarchy:

PrioritySourceOverride Behavior
1 (highest)Team Rules (dashboard)Cannot be disabled by users
2Project Rules (.cursor/rules)Override user rules
3User Rules (Cursor settings)Global defaults

Team rules are available on Team and Enterprise plans. They're set in the Cursor dashboard by admins and enforced across the organization, individual developers can't turn them off.

Within project rules, if two rules apply to the same file and conflict, the behavior isn't strictly defined. In practice, rules loaded later tend to take precedence. Numbering your files (001-base.mdc, 002-components.mdc) gives you predictable ordering.

Common Mistakes and How to Fix Them

After reading through dozens of community threads and testing rules across projects, these are the mistakes that trip people up most:

Writing rules that are too vague. "Write clean code" tells the AI nothing. "Use named exports, not default exports. Structure components as: imports, types, function, sub-components" gives it something actionable.

Making everything always-apply. Your first instinct is to set alwaysApply: true on every rule. Resist it. Audit your rules quarterly, if you have more than 2-3 always-on rules, you're probably wasting tokens.

Forgetting to test rules. After writing a rule, open a relevant file and ask Cursor to generate something that should follow the rule. If it doesn't, your glob pattern might be wrong, or the instruction isn't clear enough.

Not documenting anti-patterns. Telling the AI what to do is half the job. Telling it what not to do is the other half. Include a "NEVER do these" section in each rule with explicit examples of the wrong approach.

Ignoring rule saves in the UI. A known bug causes rule edits to disappear. If changes vanish, close Cursor completely, select "Override" on the unsaved changes popup, and reopen.

Cursor Rules vs CLAUDE.md vs AGENTS.md

Cursor isn't the only tool that uses instruction files. Here's how the formats compare for anyone working across multiple AI coding assistants:

Feature.cursor/rulesCLAUDE.mdAGENTS.md
FormatMDC with frontmatterPlain markdownPlain markdown
Glob scopingYesNoDirectory-level
Rule types4 (always, auto, agent, manual)Always-onAlways-on
Token controlFine-grainedCoarseCoarse
Version controlYesYesYes
Works inCursor onlyClaude CodeMultiple tools

Cursor's advantage is granularity. CLAUDE.md and AGENTS.md are simpler, they load everything always. Cursor lets you load the right rules at the right time, which matters once your instruction set grows beyond a few hundred lines.

For a deeper look at how context shapes AI output across these tools, our context engineering guide breaks down the principles that apply regardless of which editor you use.

FAQ

Is .cursorrules deprecated?

Yes. The single .cursorrules file at your project root still works, but Cursor recommends migrating to .cursor/rules/*.mdc files. The new format supports glob patterns, conditional loading, and better organization. Migrate by splitting your monolithic file into focused rules.

What file extension should I use, .mdc or .md?

Use .mdc for files that include YAML frontmatter (description, globs, alwaysApply). Plain .md files also work in the rules directory but don't support the frontmatter metadata that enables conditional loading.

How many rules should a project have?

Five to eight is the sweet spot for most projects. One always-on base rule, three to four auto-attached rules scoped by file type, and one or two manual rules for special tasks. More than 10 rules usually means some can be consolidated or removed.

Do Cursor rules affect autocomplete and tab completion?

Rules apply to chat and agent interactions. User Rules do not apply to inline edits (Cmd/Ctrl+K), and rules generally don't impact Cursor Tab autocomplete suggestions. They're most effective in chat and Composer sessions.

Can I share rules across multiple projects?

Yes, through Cursor's Remote Rules feature. Go to Cursor Settings > Rules, Commands, select "Remote Rule (GitHub)," and paste a repository URL. Rules auto-sync when the source repo updates. Alternatively, maintain a shared rules repo and symlink into each project.

Cursor's docs suggest keeping individual rules under 500 lines. In practice, aim for under 100 lines per rule. Shorter rules are easier to maintain and cost fewer tokens. If a rule exceeds 150 lines, split it into two focused rules.

Do rules work with all AI models in Cursor?

Rules work with every model Cursor supports, Claude, GPT-4o, Gemini, and others. The rules are injected as system-level context regardless of which model you've selected. Model behavior may vary, but the rules themselves are model-agnostic.

How do I debug a rule that isn't working?

First, verify the glob pattern matches your file, open the file and check if the rule appears in the context panel. Second, test with a direct question that should trigger the rule. Third, try setting alwaysApply: true temporarily to confirm the rule content itself works. If it does, the issue is your glob pattern.

Should I commit .cursor/rules to git?

Absolutely. The whole point of project rules is team-wide consistency. Commit everything in .cursor/rules/ except personal preference files. Add a personal.mdc to .gitignore for individual settings that shouldn't apply to everyone.

Can I use Cursor rules alongside MCP servers?

Yes, and they complement each other well. Rules define how the AI should write code, while MCP servers give the AI access to external tools and data. A rule might say "always use our internal API client," while an MCP server lets the AI actually query that API during development.

If AI features are on your roadmap, that is our specialty: Techsy's AI integration team takes LLM systems from prototype to production. Want a second opinion on your stack? Get a free consultation.

Sources

Tags

cursor rulescursor ideai codingcontext engineeringcursor rules filemdc formatai development tools

Share this article

Start Your Project

Ready to build something extraordinary?

Let's turn your vision into reality. Our team is ready to help you create software that makes a difference.