BTC 71,187.00 +0.62%
ETH 2,161.90 +0.08%
S&P 500 6,591.90 +0.54%
Dow Jones 46,429.49 +0.66%
Nasdaq 21,929.83 +0.77%
VIX 25.33 -6.01%
EUR/USD 1.09 +0.15%
USD/JPY 149.50 -0.05%
Gold 4,532.70 -0.43%
Oil (WTI) 91.50 +1.31%
BTC 71,187.00 +0.62%
ETH 2,161.90 +0.08%
S&P 500 6,591.90 +0.54%
Dow Jones 46,429.49 +0.66%
Nasdaq 21,929.83 +0.77%
VIX 25.33 -6.01%
EUR/USD 1.09 +0.15%
USD/JPY 149.50 -0.05%
Gold 4,532.70 -0.43%
Oil (WTI) 91.50 +1.31%

Cursor Rules: Advanced Pattern Configuration Guide

| 2 Min Read
Master Cursor's .cursorrules file to enforce coding standards, inject context, and teach the AI your project's unique patterns. Advanced configuration for production teams. Continue reading Cursor Rul...
SitePoint Premium
Stay Relevant and Grow Your Career in Tech
  • Premium Results
  • Publish articles on SitePoint
  • Daily curated jobs
  • Learning Paths
  • Discounts to dev tools
Start Free Trial

7 Day Free Trial. Cancel Anytime.

Cursor rules offer a mechanism for encoding repeatable coding standards directly into the AI-assisted development workflow, but most teams never move beyond ad hoc prompting. This guide covers advanced pattern configuration, production-ready templates, and debugging strategies for teams ready to codify what the AI should always do.

Table of Contents

Why Basic Cursor Instructions Fail at Scale

The Gap Between Prompting and Pattern Enforcement

Cursor rules offer a mechanism for encoding repeatable coding standards directly into the AI-assisted development workflow, but most teams never move beyond ad hoc prompting. A developer types a one-off instruction, gets a reasonable result, and moves on. This works for solo projects. It breaks down the moment a second engineer joins, a new service gets added, or two or more engineers start committing to the same service and consistency starts outweighing speed.

The core problem is that prompts are ephemeral. They exist in a single chat session, carry no institutional memory, and cannot be reviewed in a pull request. A .cursorrules file, by contrast, lives in the repository, applies automatically to every Cursor session scoped to that project, and can be versioned, diffed, and debated like any other piece of infrastructure. The gap between "telling the AI what to do each time" and "codifying what the AI should always do" is where teams lose hours correcting repeated mistakes in generated code. Cursor rules configuration, when done at an advanced level with conditional patterns and context injection hierarchies, closes that gap entirely.

The gap between "telling the AI what to do each time" and "codifying what the AI should always do" is where teams lose hours correcting repeated mistakes in generated code.

Important: This guide assumes Cursor version 0.44.x or earlier. As of Cursor 0.45+, the recommended rules format is .cursor/rules/*.mdc (Project Rules). The .cursorrules file described in this article is the legacy format, which remains supported for backward compatibility but is no longer the default mechanism. Check your Cursor version (Help → About Cursor) and consult Cursor's changelog before adopting this workflow. Rule loading behavior, format support, and context handling may differ across releases.

Anatomy of a .cursorrules File

File Location, Loading Order, and Scope

Note (check your Cursor version): Current Cursor versions (0.45+) use .cursor/rules/*.mdc as the primary rules format. The .cursorrules file described here is the legacy format, retained for backward compatibility. Verify which format your Cursor version loads before adopting this workflow.

Cursor's documented behavior for nested rule files should be verified against your installed version, as this is not formally specified in a stable public API. The following describes commonly observed behavior, which may differ across versions:

When a workspace contains nested directories, each with its own .cursorrules, Cursor resolves rules by proximity: a subdirectory rule overrides a conflicting root rule for files within that subdirectory's scope. Rules that do not conflict merge additively.

my-monorepo/
├── .cursorrules              # Root-level: shared conventions (formatting, commit style, naming)
├── packages/
│   ├── api/
│   │   ├── .cursorrules      # API-specific: Python/FastAPI patterns, Pydantic models
│   │   └── src/
│   └── frontend/
│       ├── .cursorrules      # Frontend-specific: React, Tailwind, component structure
│       └── src/
└── tools/
    └── cli/
        ├── .cursorrules      # CLI-specific: argument parsing, output formatting
        └── src/

When editing a file inside packages/api/src/, Cursor loads the root .cursorrules first, then layers the packages/api/.cursorrules on top. If both files specify an error handling convention and they conflict, the nested file wins. If the root specifies import ordering and the nested file says nothing about imports, the root rule still applies.

Before relying on this behavior for monorepo architectures, test it empirically in your Cursor version: create two .cursorrules files with a known conflict (e.g., root says "use default exports," nested says "use named exports"), generate a file in the nested scope, and observe which rule Cursor follows.

Rule Syntax Deep Dive: Sections, Directives, and Frontmatter

The .cursorrules format is plain text. The underlying LLM interprets it as structured natural language. There is no formal schema, which also means there is no validator to catch syntax errors or rule conflicts. Human review and trap-prompt testing (described below) are the only verification mechanisms. Cursor's parser responds most reliably to clearly delineated sections with consistent heading patterns. Role declarations at the top set the AI's persona. Constraint blocks define hard boundaries. Example blocks demonstrate desired output. Metadata comments help human readers navigate the file without affecting AI behavior.

# Project Context
This is a B2B SaaS billing platform. The backend is Python 3.12 + FastAPI.
The frontend is Next.js 14 with App Router and TypeScript in strict mode.

# Tech Stack
- Runtime: Node.js 20 LTS, Python 3.12
- Database: PostgreSQL 16 via SQLAlchemy 2.0
- ORM patterns: Repository pattern, no raw SQL in route handlers
- Styling: Tailwind CSS v3.4, no inline styles

# Coding Conventions
- Use named exports exclusively; no default exports.
- All functions must have explicit return type annotations.
- Prefer `const` declarations over `let` in all JavaScript/TypeScript files.
- Use absolute imports with `@/` prefix for project modules.

# Forbidden Patterns
- NEVER use `any` as a TypeScript type.
- NEVER use `console.log` in production code; use the structured logger.
- NEVER write raw SQL queries outside the `/db/queries` directory.

# Preferred Patterns
- Handle errors with custom `AppError` classes, not generic `Error`.
- Use Zod schemas for all API input validation on the frontend.
- Co-locate test files next to source files with `.test.ts` suffix.

The LLM treats each section heading as a contextual anchor. Placing forbidden patterns in their own block, separated from preferred patterns, reduces the chance of the AI conflating "never do X" with "sometimes do X."

Advanced Pattern Configuration Techniques

Defining Patterns with Conditions and Constraints

Rules become more useful when they fire only in relevant contexts rather than globally. The model follows them more reliably because it has a clear trigger rather than an always-on directive. Cursor's LLM interprets natural-language conditionals well when the trigger context is explicit and the directive is unambiguous. Positive patterns ("always do X") tend to produce more consistent results than negative patterns ("never do Y"), because the AI generates toward a concrete target rather than maintaining an absence. This is a practitioner heuristic, not a documented model behavior. That said, negative patterns remain essential for blocking known anti-patterns; they simply work best when paired with a positive alternative.

# Conditional Pattern Rules

## Component Structure
When generating React components, always use functional components
with explicit prop type interfaces. Define the interface directly
above the component in the same file. Never use React.FC.

## Error Handling
When writing catch blocks in API route handlers, always wrap the
error in an AppError instance with an appropriate HTTP status code
and a machine-readable error code string. Never re-throw raw
exceptions without wrapping.

## Import Ordering
When adding imports to any TypeScript file, follow this order:
1. Node built-in modules
2. Third-party packages
3. Internal packages (prefixed with @/)
4. Relative imports
Separate each group with a blank line.

## Test Files
When editing or generating files ending in `.test.ts` or `.spec.ts`,
never import from `src/index.ts` barrel files. Import directly from
the module under test to avoid circular dependency issues.

Each rule above uses a different conditional form: file type ("when editing test files"), task type ("when generating React components"), and code location ("in API route handlers"). Mixing these forms gives the LLM multiple context signals to determine which rules apply to a given operation.

Context Injection: Teaching the AI Your Codebase

Beyond behavioral rules, a .cursorrules file can embed architectural context that the AI would otherwise lack. This matters most for domain-specific naming and folder conventions that deviate from framework defaults. Inlining a condensed version of core domain types directly into the rules file eliminates guesswork in autocomplete and generation scenarios.

# Architecture Context
This project uses a three-layer architecture:
- Routes (/src/routes) — thin HTTP handlers, no business logic
- Services (/src/services) — business logic, receives repository interfaces
- Repositories (/src/repositories) — data access, returns domain models

All service methods accept and return domain models, never ORM entities
or raw database rows.

# Core Domain Model (condensed reference)
type Invoice = {
  id: string;              // UUIDv7 (generate via `uuidv7` npm package or `uuid` v9+ with uuidv7())
  organizationId: string;
  lineItems: LineItem[];
  status: 'draft' | 'sent' | 'paid' | 'void';
  issuedAt: Date | null;
  dueAt: Date;
  currency: 'USD' | 'EUR' | 'GBP';
};

# API Design Convention
All REST endpoints follow the pattern documented in /docs/api-standards.md.
Response envelopes use { data, meta, errors } shape. Pagination uses
cursor-based tokens, not offset/limit.

A path reference such as /docs/api-standards.md signals the document's existence to human readers of the rules file but does not cause the LLM to retrieve or read it. For conventions that must be followed precisely, inline the relevant excerpt directly, or ensure the document is open in the Cursor workspace.

Multi-File Rule Orchestration for Monorepos

In monorepo setups, the root .cursorrules should contain only conventions that genuinely apply everywhere: code formatting, commit message structure, documentation standards, and language-agnostic naming rules. Package-specific files then layer on framework and domain concerns without repeating shared rules.

Root .cursorrules (monorepo-wide):

# Shared Conventions (all packages)
- Use conventional commits: feat:, fix:, chore:, docs:, refactor:
- All public functions must have JSDoc or docstring comments.
- Maximum file length: 300 lines (team convention — adjust to match your code review norms). Split beyond that threshold.
- No circular dependencies between packages.
- Prefer composition over inheritance in all languages.

/packages/frontend/.cursorrules (React-specific):

# Frontend Overrides
This package is a Next.js 14 App Router application with TypeScript strict mode.

- Default to server components. Only add "use client" when the component
  requires useState, useEffect, or browser-only APIs.
- Place page-level data fetching in the page.tsx file, not in child components.
- Use Tailwind utility classes in this order: layout, spacing, sizing,
  typography, color, effects.
- All form state uses react-hook-form with Zod resolvers.
- Never import from packages/api directly; use the generated API client
  in /src/lib/api-client.ts.

The frontend file does not restate the conventional commits rule or the 300-line limit. Those apply automatically from the root. It only adds or overrides what is specific to the frontend package.

Production-Ready .cursorrules Templates

Template 1: Next.js + TypeScript Full-Stack Application

Use this when building a full-stack application with Next.js App Router, TypeScript in strict mode, and Tailwind CSS.

# Role
You are a senior full-stack TypeScript developer specializing in Next.js 14.

# Tech Stack
- Next.js 14 (App Router), React 18, TypeScript 5.x strict mode
- Styling: Tailwind CSS v3.4
- Validation: Zod
- Database: Prisma ORM with PostgreSQL
- Auth: NextAuth.js v4 (stable) or NextAuth.js v5 (verify release status before production use — v5 had a significantly changed API surface and was in beta as of early 2025)

# Conventions
- Default to React Server Components. Add "use client" only when required.
- Use named exports exclusively. No default exports except page.tsx/layout.tsx.
- All component props must have an explicit TypeScript interface.
- Use absolute imports with @/ prefix.
- Tailwind class order: layout, spacing, sizing, typography, color, state.
- Fetch data in page.tsx or layout.tsx, pass as props to children.
- Use server actions for mutations; no API route handlers for form submissions.

# Forbidden
- NEVER use `any` or `as any` type assertions.
- NEVER use inline style attributes.
- NEVER use `useEffect` for data fetching; use server components or SWR.
- NEVER disable TypeScript strict checks with @ts-ignore. Prefer `@ts-expect-error` over `@ts-ignore` when suppression is genuinely necessary; `@ts-expect-error` fails if the suppressed error disappears, preventing stale suppressions.

# Error Handling
- Wrap async operations in try/catch with typed error responses.
- Use error.tsx boundary files per route segment.

The server component default eliminates the most common Next.js 14 mistake: unnecessary client bundles. Banning any at the rules level catches type erosion before code review. Specifying Tailwind class ordering prevents meaningless diffs when multiple developers touch the same component.

Template 2: Python FastAPI Microservice

Use this for Python microservices built on FastAPI with Pydantic validation and async handlers.

# Role
You are a senior Python backend engineer building async microservices.

# Tech Stack
- Python 3.12, FastAPI 0.109+, Pydantic v2
- Database: SQLAlchemy 2.0 async with PostgreSQL
- Task queue: Celery 5.x with Redis broker
- Logging: structlog

Note: Pydantic v2 is not backward compatible with v1. If upgrading an
existing service, review the Pydantic v2 migration guide before applying
these rules.

# Conventions
- All route handlers must be async functions.
- Use Pydantic BaseModel for request/response schemas; never use raw dicts.
- Dependency injection via FastAPI Depends() for services and repositories.
- All database queries go through repository classes, never in route handlers.
- Use structlog for all logging; include request_id in every log entry.
- Type-annotate all function signatures including return types.

# Forbidden
- NEVER use `print()` for logging.
- NEVER use synchronous database calls.
- NEVER catch bare `Exception` without re-raising or wrapping.

# Testing
- Use pytest with pytest-asyncio for async tests.
- Mock external services at the repository boundary, not at the HTTP level.
  Repository-level mocking produces faster, more deterministic tests
  because it removes network I/O and serialization from the test path.

Forcing Pydantic v2 models for all I/O ensures automatic validation and OpenAPI schema generation. The structlog requirement with request_id creates traceable logs across distributed calls.

Positive patterns ("always do X") tend to produce more consistent results than negative patterns ("never do Y"), because the AI generates toward a concrete target rather than maintaining an absence.

Template 3: React Native Mobile App

Use this for cross-platform mobile applications with React Native, TypeScript, and Expo.

# Role
You are a senior React Native developer building cross-platform mobile apps.

# Tech Stack
- React Native 0.76, Expo SDK 52, TypeScript strict mode
  Note: Expo SDK versions are tightly coupled to specific React Native
  versions. Verify compatibility at docs.expo.dev/versions/latest/
  before upgrading either dependency.
- Navigation: Expo Router (file-based)
- State: Zustand for global state, React Query for server state
- Styling: StyleSheet.create(), no inline style objects

# Conventions
- Use platform-specific files (.ios.tsx, .android.tsx) only when truly needed.
- All screens live in /app directory following Expo Router conventions.
- Shared components go in /components with index.ts barrel exports.
- All touchable elements must have accessibilityLabel and accessibilityRole.
- Use React Query for all API calls; no useEffect-based fetching.

# Forbidden
- NEVER use pixel values without scaling; use responsive sizing utilities.
- NEVER use Alert.alert for user-facing errors; use the custom toast system.
- NEVER import directly from react-native when an Expo equivalent exists.

# Accessibility
- Every interactive element requires accessibilityLabel.
- Screen readers must be able to navigate all list items.
- Minimum touch target: 44x44 points.

Mandating accessibilityLabel at the rules level prevents accessibility gaps from becoming a code review afterthought. Separating Zustand (client state) from React Query (server state) avoids the common pattern of duplicating server data in global stores. The Expo import preference keeps bare React Native modules from conflicting with managed workflow builds.

Template 4: Laravel + Inertia.js Monolith

Use this for monolithic PHP applications using Laravel with Inertia.js and Vue 3 on the frontend.

# Role
You are a senior Laravel developer building a monolith with Inertia.js.

# Tech Stack
- PHP 8.3, Laravel 11, Inertia.js 1.x
- Frontend: Vue 3 Composition API, TypeScript
- Database: MySQL 8, Eloquent ORM
- Auth: Laravel Breeze with Inertia stack

# Conventions
- Use Form Request classes for all input validation; never validate in controllers.
- Controllers must be single-action invokable classes or resource controllers.
- Return Inertia::render() from controllers; never return JSON from web routes.
- Eloquent models define fillable, casts, and relationships explicitly.
- Vue components use <script setup lang="ts"> syntax exclusively.
- Use useForm() from @inertiajs/vue3 for all form submissions.

# Forbidden
- NEVER use raw `DB::` queries in controllers, services, or Eloquent models. Raw queries are permitted in database migrations, seeders, and explicitly designated performance-optimization query files.
- NEVER pass Eloquent models directly to Inertia; use API Resources.
- NEVER use Options API in Vue components.
- NEVER use Blade templates for Inertia pages.

# Structure
- Controllers: app/Http/Controllers/{Domain}/
- Form Requests: app/Http/Requests/{Domain}/
- Vue Pages: resources/js/Pages/{Domain}/

Forcing API Resources (Laravel's built-in serialization layer for transforming Eloquent models into JSON) between Eloquent and Inertia prevents accidental exposure of database columns or relationships to the frontend. Single-action invokable controllers keep the codebase navigable as the route count grows. The domain-based folder convention groups related controllers, requests, and pages to make feature work more self-contained.

Template 5: Node.js CLI Tool

Use this for command-line tools built with Node.js, TypeScript, and a command framework.

# Role
You are a senior Node.js developer building developer-facing CLI tools.

# Tech Stack
- Node.js 20 LTS, TypeScript 5.x strict mode
- Command framework: Commander.js
- Output: chalk for colors, ora for spinners
- Config: cosmiconfig for user configuration files
- Testing: Vitest with inline snapshots

# Conventions
- Each command lives in its own file under /src/commands/.
- Commands export a function that receives parsed args and returns exit code.
- All user-facing output goes through /src/output.ts helper functions.
- Errors print to stderr with red formatting and a non-zero exit code.
- Use process.exitCode assignment, never process.exit() directly.
  Note: Setting process.exitCode without calling process.exit() requires
  the event loop to drain naturally. If long-lived listeners exist (e.g.,
  open database connections or HTTP servers), the process may not exit.
  Ensure async cleanup completes.
- Arguments use kebab-case flags; no single-letter aliases without long form.

# Forbidden
- NEVER use console.log directly; use the output helper module.
- NEVER swallow errors silently; always print to stderr and set exit code.
- NEVER use synchronous fs methods; use fs/promises.

# Testing
- Co-locate test files: command.ts → command.test.ts in same directory.
- Snapshot test all help text output.
- Mock process.argv via Commander's parseAsync() with explicit args array.

Routing all output through a helper module makes it trivial to add JSON output mode or quiet flags later without rewriting every command. Avoiding process.exit() allows async cleanup handlers and test runners to function correctly. Snapshot testing help text catches unintentional changes to the user-facing interface during refactors.

Debugging and Iterating on Your Rules

Testing Rule Effectiveness

A practical spot-check is to deliberately prompt Cursor to violate a rule and observe whether it holds. Think of this as a "trap prompt." Ask it to generate a React component and check whether it defaults to server components. Ask for error handling and see whether it reaches for the custom AppError class or a generic throw new Error(). This technique surfaces rules the model ignores, typically because they are too vague, buried in a long file, or contradicted by another rule.

Common failure modes include: rules you phrase as suggestions instead of directives (the AI treats "consider using" differently from "always use"), conflicting rules at different directory levels that the developer forgot about, and overly long .cursorrules files that push key instructions outside the effective context window. That window is the portion of the model's total token budget that Cursor allocates to system-level instructions: your rules file, open file contents, and conversation history. As open files and conversation length grow, the token budget available for rules shrinks. Files beyond roughly 500 lines risk pushing late rules out of context in sessions with many open files. Empirically test compliance with your specific setup rather than relying on a fixed line count.

Versioning and Team Collaboration

The .cursorrules file belongs in version control. Changes to it should go through the same pull request review process as changes to linting configurations or CI pipelines. Section headers and version metadata make the file scannable for teammates who need to understand why a rule exists.

# ==================================================
# .cursorrules — Billing Platform
# Version: X.Y.Z
# Last updated: YYYY-MM-DD
# Owner: Platform Engineering Team
# ==================================================

# --- Shared Conventions ---
# (Updated vX.Y.0: added structured logging requirement)

# --- Forbidden Patterns ---
# (Updated vX.Y.Z: banned console.log per incident #NNNN)

Version metadata and change annotations turn the file into a living document with institutional memory, making it clear when and why each rule was introduced.

Common Pitfalls and How to Avoid Them

Over-Specifying Rules (Context Window Bloat)

Cramming every possible coding preference into a single .cursorrules file dilutes the important directives. When the file exceeds the effective context window, the LLM pays less attention to later rules. Prioritize rules that address recurring mistakes in generated code, not rules that a linter or formatter already enforces.

Under-Specifying Rules (Ambiguous Directives)

A rule like "write clean code" gives the AI no actionable constraint. Rules need specificity: name the pattern, the file scope, and the expected output shape. Compare "use good error handling" with the conditional error handling rule shown earlier, which specifies wrapping in AppError with an HTTP status code and a machine-readable error code string.

A rule like "write clean code" gives the AI no actionable constraint. Rules need specificity: name the pattern, the file scope, and the expected output shape.

Rule Conflicts Across Directory Levels

When a root file says "use semicolons" and a nested file says "no semicolons," the outcome depends on Cursor's load order for a given context. Audit nested .cursorrules files periodically to ensure they complement rather than contradict root-level rules.

Writing Rules for the Developer Instead of for the AI

Rules like "remember to run tests before committing" instruct the human, not the code generation model. Every directive in the file should constrain or guide what the AI produces. Developer workflow reminders belong in a CONTRIBUTING.md file.

Key Takeaways

Place shared conventions at the monorepo root, layer package-specific overrides in subdirectories, and keep files concise enough to fit within the effective context window. Rule architecture determines whether the AI follows your standards or ignores them.

Scoping rules to file types, task types, and code locations gives the LLM clear signals about when each directive applies. Conditional patterns outperform blanket instructions because they reduce noise.

Templates accelerate adoption. Start with the production-ready template closest to the project's stack, customize it in a single focused session, and commit it to the repository so the entire team benefits immediately.

Verify your Cursor version. Rule loading behavior, supported formats, and context handling change across Cursor releases. Confirm whether .cursorrules or .cursor/rules/*.mdc is active in your environment before investing in rule architecture.

SitePoint TeamSitePoint Team

Sharing our passion for building incredible internet things.

Comments

Please sign in to comment.
Capitolioxa Market Intelligence