Agentbrisk

How to Write Cursor Rules in 2026: Real Examples for Every Stack

April 15, 2026 · Editorial Team · 8 min read · cursorcursorrulesai-coding

Cursor's rules system is one of those features that developers either ignore completely or become obsessed with. If you're in the "ignore" camp, you're probably getting decent results but fighting the model on the same corrections over and over. If you've started using rules but your files have grown into 800-line monsters, you're probably not getting much benefit either.

This guide covers the practical middle ground: what goes in a .cursorrules file, how to scope rules correctly, and real examples for Python, TypeScript, and Rust projects that you can adapt directly.


How Cursor rules work in 2026

Cursor has two types of rules: global rules in Settings and project-level rules in .cursor/rules/ (the old .cursorrules file format still works, but the directory approach is newer and more flexible).

Global rules apply to every project. These are for personal preferences that cross all contexts: code style preferences, how you like explanations formatted, languages you prefer answers in. Keep global rules short. They're injected into every conversation regardless of what you're working on.

Project rules in .cursor/rules/ can be organized as multiple files. Each .mdc file in that directory becomes a rule set. You can have coding-conventions.mdc, testing.mdc, database.mdc as separate files. Cursor loads them all for conversations in that project.

The .cursorrules file in the project root is the legacy approach that still works fine. Many developers stick with it because a single file is easier to version and review. Both approaches work; the directory system just gives you more organization.


What belongs in global rules

Keep global rules to things that are truly cross-project. A reasonable global setup:

You are an expert software engineer. When helping with code:
- Write concise explanations, not lectures
- Prefer showing code over describing code
- Point out potential issues you notice even if not directly asked
- Ask for clarification rather than guessing when requirements are ambiguous
- When editing existing code, match the surrounding style unless I ask you to change it

That's genuinely useful globally. It shapes the tone and approach without making assumptions about any specific stack.

What doesn't belong in global rules: anything stack-specific, anything about a particular project's structure, version numbers, or conventions that won't apply elsewhere.


TypeScript project rules

Here's a working .cursorrules file for a modern TypeScript project (Next.js 15, TypeScript 5.4):

## Project: TypeScript / Next.js App Router

### Stack
- Next.js 15.2 with App Router
- TypeScript 5.4, strict mode, no implicit any
- Tailwind CSS 4
- Drizzle ORM with PostgreSQL
- Zod for validation
- tRPC for API
- Vitest for unit tests, Playwright for e2e

### TypeScript conventions
- Use `type` for object shapes, `interface` only when extending
- Never use `any`. Use `unknown` and narrow properly
- Prefer `const` assertions over type widening where intent is clear
- All async functions return explicit types, not inferred
- Discriminated unions over boolean flags for state (prefer `{status: 'loading'} | {status: 'error', error: Error}`)

### File structure
- Components: /src/components/ (shared) or co-located with the route that uses them
- Server actions: /src/actions/ 
- Database queries: /src/db/queries/ only, nowhere else
- Types: /src/types/ for shared types; co-locate component-specific types
- Utilities: /src/lib/

### React / Next.js
- Server Components by default, add 'use client' only when needed
- Data fetching in Server Components or Server Actions, never client-side fetch
- No useEffect for data fetching
- Loading states via loading.tsx and Suspense, not manual isLoading booleans
- Error boundaries via error.tsx

### Do not
- Do not use class components
- Do not use pages/ router patterns (App Router only)
- Do not write barrel exports (index.ts re-exports)
- Do not add console.log (use structured logging in /src/lib/logger.ts)
- Do not install new packages without confirming first

This is about 40 lines. It's detailed enough to be useful but short enough that every line gets attention.


Python project rules

Python rules need more coverage because the ecosystem has more variability. Here's a working example for a FastAPI backend:

## Project: Python / FastAPI Service

### Environment
- Python 3.13
- FastAPI 0.115.x
- SQLAlchemy 2.x with async engine (asyncpg driver)
- Pydantic v2 for validation
- pytest + pytest-asyncio for tests
- ruff for linting (config in pyproject.toml)

### Code style
- Type annotations on all function signatures (parameters and return types)
- Use `X | None` syntax instead of `Optional[X]` (Python 3.10+ style)
- Dataclasses or Pydantic models for structured data, never plain dicts
- f-strings for string formatting everywhere
- Pathlib for file paths, never os.path

### Project structure
- API routes: /app/api/v1/routers/
- Business logic: /app/services/ (never in routers)
- Database models: /app/models/
- Pydantic schemas: /app/schemas/
- Database queries: /app/repositories/ (repository pattern)
- Utilities: /app/utils/

### FastAPI specifics
- Dependency injection for database sessions and auth (via Depends())
- Use response_model on all endpoints
- Background tasks via BackgroundTasks, not threading
- Lifespan for startup/shutdown, not @app.on_event (deprecated)
- HTTPException with specific status codes, never generic 500 responses

### SQLAlchemy
- Async sessions only, never sync
- Explicit column types, never rely on inference
- Relationships loaded explicitly (no lazy loading)
- All queries in /app/repositories/, not scattered in services

### Testing
- pytest fixtures in conftest.py
- Async test functions with @pytest.mark.asyncio
- Use httpx.AsyncClient for API tests, not TestClient
- Mock external services with pytest-mock

### Do not
- Do not use global state
- Do not use print() (use logging module)
- Do not catch bare Exception without logging
- Do not write synchronous DB code in async functions

Notice that the "Do not" section is specific. "Do not use global state" is vague. "Do not use synchronous DB code in async functions" is specific enough that the model can actually follow it.


Rust project rules

Rust has its own culture and best practices that differ meaningfully from what general-purpose models default to:

## Project: Rust CLI / Library

### Environment
- Rust 1.87 stable
- Edition 2024
- Cargo workspace with crates: cli, core, storage

### Code style
- Follow rustfmt defaults (auto-applied)
- Clippy pedantic warnings treated as errors in CI
- Prefer iterators over explicit loops
- Use `?` for error propagation, never .unwrap() in library code
- In CLI code, .expect("descriptive message") is acceptable at startup only

### Error handling
- Custom error types via thiserror crate
- Return Results from all fallible public functions
- Error messages should describe what failed, not what the caller was trying to do
- Never panic in library code (use ? or return Err)

### Ownership / lifetimes
- Prefer owned types in structs unless there's a clear performance reason for references
- When lifetimes are needed, document why in a comment
- Avoid Rc/RefCell unless you've exhausted other options

### Dependencies
- Current key crates: tokio (async), serde/serde_json, anyhow (for CLI error handling), thiserror (for library errors), clap (CLI parsing), tracing (logging)
- Do not add new dependencies without discussing first
- Prefer std over external crates for simple operations

### Async
- tokio::spawn for concurrent tasks
- Use channels (tokio::sync::mpsc) for communication between tasks, not shared mutexes
- async_trait for async methods in traits (or use RPITIT if the trait is simple enough)

### Documentation
- Doc comments on all public types and functions
- Include an example in doc comments for any non-obvious function
- //! crate-level docs in lib.rs

### Do not
- Do not use unsafe unless it's in a clearly delimited module with a safety comment
- Do not use std::sync::Mutex for high-contention code (use tokio::sync::Mutex or RwLock)
- Do not impl Display for error types (thiserror handles this)

What makes a rule file fail

After seeing a lot of .cursorrules files, a few patterns consistently produce bad results.

Too long. Once a rules file goes past 200 lines, the model starts ignoring parts of it. Not all parts, unpredictably. Long rules files give you false confidence that you've covered everything. Write fewer, more important rules instead.

Contradictions. "Always add type annotations" and "Match the surrounding code style" will conflict on a codebase with mixed annotation coverage. The model will pick one and ignore the other. Audit your rules for conflicts.

Generic statements that the model already follows. "Write clean, readable code" is not a rule. The model assumes this anyway. Use the space for things that are specific to your project or that contradict the model's defaults.

Outdated API references. If your rules say "use Express 4 middleware pattern" but your project has migrated to Express 5, the rules are now working against you. Treat rules files like documentation: they go stale.

No context on why. Rules like "do not use barrel exports" leave the model guessing when it hits an edge case. "Do not use barrel exports (causes slow cold starts in Next.js App Router)" tells the model enough to make a judgment call in unusual situations.


A minimal starter template

If you're starting fresh, here's the minimum viable .cursorrules:

## Stack
[List your main framework, language version, and key libraries]

## Key conventions
[3-5 project-specific things the model won't know by default]

## File structure
[Where things live, so the model knows where to put new files]

## Do not
[Things that are easy to get wrong or that contradict defaults]

Fill those four sections and you'll get most of the benefit. You can always add more detail later when you notice the model getting something wrong repeatedly.


Keeping rules in sync with the project

The main maintenance burden for rules files is keeping them accurate as the project evolves. Two practices that help:

When you migrate a major dependency or change a convention, update the rules file in the same PR. It's a two-minute habit that prevents the rules from drifting.

When you find yourself correcting the model on the same thing three times in one week, that's a signal: add it to the rules. The correction cost is much lower than continuing to re-correct.


For prompting patterns that work alongside your rules configuration, the AI coding agent prompt engineering guide covers the runtime prompting layer. And for CLAUDE.md specifically (Claude Code's equivalent to .cursorrules), the structure and approach are very similar, but there are differences worth knowing about.

Search