Cursor Rules Files are configuration files that define how a cursor-based editor or IDE applies logic to user interactions, document structure, and code context. They are essential when a tool needs to make intelligent decisions—such as auto-completion, navigation, formatting, or refactoring—based on the current position of the cursor in a document, and when those decisions must adapt to different file types, project structures, or user workflows. They matter most when editing code, prose, or markup where context-aware behavior improves productivity: when the cursor is over a function name, a variable, a section heading, or a table cell, and the tool must act based on that context.

Defining a Cursor Rule

A cursor rule is a named, reusable piece of logic that activates when the cursor meets one or more conditions. It defines actions to run, such as inserting text, changing selection, triggering commands, or updating metadata.

Each rule is defined in a file using a structured format, typically JSON or YAML, and is applied across one or more files in a project. The root of a Cursor Rules Files configuration is a collection of rules, each with a unique name and a set of conditions and actions.

Rule Structure and Syntax

A minimal rule in a cursor-rules.json file looks like:

{
  "rules": [
    {
      "name": "insert-async-await",
      "description": "Add async/await boilerplate when cursor is on a function call",
      "conditions": [
        {
          "type": "cursor-in-function-call",
          "arguments": {
            "function-name": "fetch",
            "file-type": "javascript"
          }
        },
        {
          "type": "selection-empty",
          "arguments": {}
        }
      ],
      "actions": [
        {
          "type": "insert-text",
          "text": "await "
        },
        {
          "type": "move-cursor",
          "offset": 6
        },
        {
          "type": "set-selection",
          "start": 0,
          "end": 6
        }
      ]
    }
  ]
}

The name field is required and serves as the rule’s identifier. The description is optional but recommended for documentation.

Conditions are evaluated in order. A rule activates only when all conditions are satisfied. The type field identifies the condition type, and arguments provides context-specific parameters.

Common Condition Types

  • cursor-in-function-call: The cursor is inside the parentheses of a function call. Arguments include function-name (string), file-type (string), and min-arguments (integer).
  {
    "type": "cursor-in-function-call",
    "arguments": {
      "function-name": "map",
      "file-type": "typescript",
      "min-arguments": 1
    }
  }
  • selection-empty: The current selection has zero length.
  { "type": "selection-empty" }
  • cursor-on-variable: The cursor is over a variable name, optionally matching a pattern.
  {
    "type": "cursor-on-variable",
    "arguments": {
      "pattern": "^\\w+\\s*=\\s*"
    }
  }
  • file-type: The current file has a specific extension or MIME type.
  {
    "type": "file-type",
    "arguments": {
      "extension": "md"
    }
  }

Common Action Types

  • insert-text: Inserts text at the cursor position.
  {
    "type": "insert-text",
    "text": "console.log()"
  }
  • move-cursor: Moves the cursor by a given number of characters from the current position.
  {
    "type": "move-cursor",
    "offset": 12
  }
  • set-selection: Sets a selection from start to end character indices.
  {
    "type": "set-selection",
    "start": 0,
    "end": 10
  }
  • run-command: Executes a shell command in the project root.
  {
    "type": "run-command",
    "command": "npm run test",
    "output": "inline"
  }
  • update-metadata: Adds or updates metadata in a document’s front matter or header.
  {
    "type": "update-metadata",
    "key": "status",
    "value": "draft"
  }

Advanced: Nested and Composed Rules

Rules can reference other rules via extends or include.

{
  "name": "format-html",
  "extends": "format-code",
  "conditions": [
    { "type": "file-type", "arguments": { "extension": "html" } }
  ],
  "actions": [
    { "type": "run-command", "command": "prettier --write ." }
  ]
}

When a rule extends another, it inherits all conditions and actions, then applies its own. This enables modular, reusable logic.

Rule Evaluation and Order

Rules are evaluated in the order they appear in the rules array. The first rule whose conditions all pass is applied. If a rule has match-all: true, it applies only when all conditions are met, not just one.

A rule can also have priority to influence evaluation order when multiple rules match.

{
  "name": "add-test-case",
  "priority": 100,
  "conditions": [
    { "type": "cursor-on-variable", "arguments": { "pattern": "^test\\w+" } }
  ],
  "actions": [
    {
      "type": "insert-text",
      "text": "\n\n// Test case: "
    }
  ]
}

Priority is a number. Higher values indicate higher precedence. Rules with equal priority are evaluated in declaration order.

Applying Rules Across Files

Rules are applied per file. The editor or tool reads the cursor-rules.json file in the project root. If no file is found, it uses a default set of rules.

If a project has multiple cursor-rules.* files, the tool merges them. The merge order is: cursor-rules.json, cursor-rules.yaml, cursor-rules.yml, in that priority order. Each file overrides the previous.

The rule engine evaluates rules on every cursor move, selection change, or file open. It computes the set of active rules and applies them in priority order.

Failure Modes and Silent Pitfalls

Condition Evaluation Fails Silently

A common failure: the cursor-on-variable condition returns false even when the cursor is over a variable. This happens when the variable name does not match the expected pattern.

For example, with:

{
  "type": "cursor-on-variable",
  "arguments": { "pattern": "^\\w+\\s*=\\s*" }
}

If the cursor is on user.name, but the variable is defined as user = data, the pattern ^\\w+\\s*=\\s* expects name = but not user.name =.

The tool does not report the mismatch. Instead, the rule is silently skipped.

Actions Not Applied in Order

Actions are executed in the order listed, but only if the rule is active. A rule may be active, but its actions are applied in a different context.

For example, move-cursor applies relative to the current cursor position, not the start of the rule evaluation.

If a rule has:

{
  "actions": [
    { "type": "insert-text", "text": "const " },
    { "type": "move-cursor", "offset": 5 }
  ]
}

And the cursor is at position 10, the insert-text adds "const " at 10, then move-cursor moves the cursor from 10 to 15. But if the insert-text shifts the content, the new cursor position may be at 15, but the move-cursor offset is relative to the pre-insertion state.

This leads to unexpected cursor positions when the inserted text changes the document.

Missing file-type Evaluation

A rule condition file-type uses the file’s extension, but it may not account for language-specific file types.

For example, a file app.graphql is not recognized as graphql unless the tool explicitly maps .graphql to graphql MIME type.

If a rule expects file-type: graphql, but the file is app.gql, it fails silently. The rule does not apply.

Rule Conflicts and Overlapping Actions

When two rules apply simultaneously, their actions are applied in priority order, but actions may conflict.

For example:

  • Rule A: insert-text "import { " at cursor.
  • Rule B: insert-text "from '..." at cursor.

If Rule A runs first, then Rule B, the result is:

import { from '...

But if Rule B runs first, the result is:

from '...import { 

The order of rule application and action execution must be carefully coordinated.

Metadata Updates Are Not Saved

When a rule runs update-metadata, the metadata is applied in memory but not written to disk unless explicitly triggered.

A rule may add status: draft, but the user must save the file or trigger a save-metadata action for the change to persist.

Errors in Rule Configuration

The rule engine may fail silently when parsing a rule file.

For example, a typo in a type field:

{
  "type": "cursor-on-variabel",  // typo: 'variabel' instead of 'variable'
  "arguments": { "pattern": "^\\w+" }
}

The engine does not recognize cursor-on-variabel, so the condition is skipped. No error message is logged.

Similarly, an unknown action type:

{
  "type": "run-external-command",
  "command": "echo 'hello'"
}

The engine does not know run-external-command, so the action is ignored.

Rule File Validation

A best practice is to validate cursor-rules.json against a schema.

A minimal schema defines required fields and types:

{
  "type": "object",
  "required": ["rules"],
  "properties": {
    "rules": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["name", "conditions", "actions"],
        "properties": {
          "name": { "type": "string" },
          "description": { "type": "string" },
          "priority": { "type": "integer", "default": 0 },
          "match-all": { "type": "boolean", "default": false },
          "conditions": {
            "type": "array",
            "items": {
              "type": "object",
              "required": ["type"],
              "properties": {
                "type": { "type": "string" },
                "arguments": { "type": "object" }
              }
            }
          },
          "actions": {
            "type": "array",
            "items": {
              "type": "object",
              "required": ["type"],
              "properties": {
                "type": { "type": "string" },
                "arguments": { "type": "object" }
              }
            }
          }
        }
      }
    }
  }
}

This schema ensures that every rule is valid and that the rule engine can catch configuration errors early.

Using Rules with the Editor

To apply a rule, the editor reads the rules file on startup or when a file is opened.

The tool computes the active rules for the current file. It then evaluates each rule’s conditions. When a rule matches, its actions are executed in order.

The editor may provide a debug view that shows:

  • Which rules are active.
  • For each rule, which conditions passed and which failed.
  • A log of executed actions.

This helps users understand why a rule triggered and how to tune it.

Example: Full Configuration File

{
  "rules": [
    {
      "name": "add-async-keyword",
      "description": "Add 'async' keyword when cursor is in a function body",
      "priority": 150,
      "conditions": [
        { "type": "cursor-in-function-body" },
        { "type": "file-type", "arguments": { "extension": "js" } }
      ],
      "actions": [
        {
          "type": "insert-text",
          "text": "async "
        },
        {
          "type": "move-cursor",
          "offset": 5
        }
      ]
    },
    {
      "name": "create-test-file",
      "description": "Create a new test file when cursor is on a module",
      "conditions": [
        { "type": "cursor-on-variable", "arguments": { "pattern": "^module\\." } },
        { "type": "selection-empty" }
      ],
      "actions": [
        {
          "type": "run-command",
          "command": "mkdir -p tests && touch tests/test-${cursor-value}.js"
        },
        {
          "type": "open-file",
          "file": "tests/test-${cursor-value}.js"
        }
      ]
    }
  ]
}

This configuration enables a developer to write a function, place the cursor inside it, and press a key to automatically add async at the start. When working with modules, they can place the cursor on module.utils and trigger a command to generate a test file.

Summary

Cursor Rules Files are a declarative way to extend an editor’s intelligence. They let users define logic that activates based on cursor position and file context, without writing code. The format is simple, composable, and predictable. The key to success is understanding how conditions and actions interact, and how order and context affect execution. Common pitfalls—silent condition failures, misapplied actions, and unvalidated configuration—can be caught with careful design and tooling.

This page was rewritten on 11 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.