Cursor now has two ways to teach its agent something: rules and skills. Rules got all the early attention, so most guides you find are about .mdc files. But since Cursor shipped Agent Skills, the right home for a lot of that content changed, and surprisingly little has been written about how to build one well. This is that guide: the real file mechanics from Cursor's docs, plus what we've learned from generating and linting thousands of agent context files.
What a Cursor Skill is (and when a rule is the wrong tool)
A Cursor rule is passive context. It lives in .cursor/rules/*.mdc, and depending on its frontmatter it's either injected into every request (alwaysApply: true) or attached whenever a file matching its globs enters the conversation. The agent never chooses to use a rule. The rule is simply there, spending tokens whether or not it's relevant to the current task.
A skill is an on-demand capability. It lives in its own folder (.cursor/skills/<name>/SKILL.md), and by default the agent only sees its name and description. When the agent decides the skill is relevant to what you asked, it pulls in the full SKILL.md, and only then. You can also invoke one explicitly by typing /skill-name in Agent chat. A skill folder can additionally carry scripts/ the agent can execute, references/ docs it loads only when needed, and assets/ such as templates. Rules can't do any of that.
That difference dictates the choice:
- Rule: a standing constraint that should shape everything the agent does in some slice of the codebase. "We use named exports." "Never edit generated files, regenerate them instead."
- Skill: a procedure the agent performs when asked. "Create a database migration." "Write a changelog entry." "Cut a release."
If you're describing how code should always look, you want a rule (we have a free generator for those too). If you're describing how to do a task, you want a skill.
Anatomy of a skill
The minimum viable skill is one folder containing one file. Project skills go in .cursor/skills/ (or .agents/skills/), get committed to git, and ship to your whole team. Personal skills go in ~/.cursor/skills/ and follow you across every project. Cursor discovers SKILL.md files recursively and also reads legacy paths like .claude/skills/, so skills written for other agents usually work as-is.
Here's a complete, realistic skill for a project that manages its database with numbered SQL migrations:
---
name: db-migration
description: Create a new numbered SQL migration for the Supabase database.
Use when the user asks to add a migration, change the schema, create a
table, add a column, or write RLS policies.
paths:
- "supabase/migrations/**"
---
# db-migration
Create exactly one migration file per schema change in supabase/migrations/.
## Workflow
1. Find the next number: run `scripts/next-number.sh` from the skill root.
2. Name the file `NNN_short_description.sql` (three digits, snake_case).
3. Write the forward migration only. This project does not use down
migrations; reverting means writing a new forward migration.
4. Enable RLS on every new table and add an owner-only policy, because
every user-data table in this repo is RLS-guarded.
5. Run `supabase db push` against the local instance and paste the output.
## Restrictions
- Never edit an already-committed migration, because it may have run in
production. Create a new migration instead.
The frontmatter fields, per Cursor's docs:
name(required): lowercase letters, numbers, and hyphens, and it must match the folder name. This is also what you type after/to invoke it manually.description(required): what the skill does and when to use it. This is the only part of the body the agent sees before deciding to load the skill, which makes it the single highest-leverage line in the file.paths(optional): glob patterns that scope the skill to matching files. The legacyglobskey still works, butpathsis the current form.disable-model-invocation(optional): settrueand the skill only runs when a human types/skill-name, like a traditional slash command. Use it for destructive or expensive procedures (deploys, data backfills) you never want triggered on a hunch.
Everything below the frontmatter is plain markdown instructions, written for the agent, not for humans.
Writing the description so the agent actually triggers it
This is where most skills die. Discovery works like a routing table: the agent matches your request against every skill's description and loads the body only for the one it judges relevant. A vague description means a skill that never fires, and you'll conclude skills "don't work" when really your one-line summary lost the routing decision.
Three rules that fix it:
- State what it does and when to use it, in the third person. Not "Helps with migrations" but "Creates a numbered SQL migration. Use when the user asks to change the schema, add a table, or add a column."
- Include the words people actually type. If your team says "add a column" and "write an RLS policy," those phrases belong in the description verbatim. The router matches your vocabulary, not your intent.
- Say when not to use it if there's a near-neighbor. Two skills with overlapping descriptions (say,
db-migrationanddb-seed) will steal each other's invocations unless each one names its boundary.
If the skill still won't fire, test it directly with /skill-name. If it works when invoked manually but never automatically, the body is fine and the description is the problem. Rewrite the description, not the instructions.
Scoping: what belongs in the skill vs a rule
A useful test for every line you're about to write: "does this apply always, or only during this task?"
- "All SQL is lowercase with leading commas" applies always. Rule.
- "Run
scripts/next-number.shto pick the migration number" applies only during the task. Skill. - Repo-wide facts ("we deploy on Cloudflare Workers") belong in neither. They go in your always-on context file, and both rules and skills can assume them.
Keeping this boundary clean pays twice: rules stay small (they're paid for on every request), and skills stay self-contained instead of behaving differently depending on which rules happen to be loaded.
Common mistakes
We lint a lot of agent config files, and the same failures show up in skills:
Vague trigger descriptions. "Helps with database stuff." The router has nothing to match on, and our linter would flag the equivalent instruction body as vague/low-signal: no checkable content, nothing the agent can act on.
Skills that should be rules. A "skill" whose body is a list of formatting conventions has no procedure to perform. It will fire rarely and randomly, and the conventions won't apply the other 95% of the time. Convert it to an auto-attached rule with globs and it works every time the relevant files are open.
Bloat. Skills are loaded on demand, which tempts people to dump entire style guides and API references into SKILL.md. But once triggered, the whole file lands in context. Keep SKILL.md to the procedure and push detail into references/, which the agent loads progressively, only when a step actually needs it. This mirrors the size budgets that exist for every context file format.
Shouting and bare prohibitions. Instructions written for 2024-era models, "CRITICAL: you MUST ALWAYS...", degrade current ones, which over-trigger on aggression (antipattern/emphasis-inflation). And a "NEVER do X" with no reason generalizes worse than one with its rationale attached (antipattern/bare-prohibition). Note how the example above says why committed migrations can't be edited.
You can lint a skill body for free to catch these before they cost you a bad agent run.
Validating and iterating
- Manual smoke test. Invoke it with
/skill-nameand watch it perform the procedure end to end. This isolates the instruction body from the routing problem. - Discovery test. Open a fresh Agent chat and phrase the request the way a teammate would, without naming the skill. If it doesn't load, iterate on the description only.
- Lint it. Run the body through the free linter to catch vague directives, emphasis inflation, and unexplained prohibitions.
- Watch real usage. When the agent misuses the skill, the fix is usually one of: a missing "when to use" phrase in the description, a step that assumed knowledge the agent didn't have, or scope creep that belongs in a rule.
Skip the blank page
You can hand-write your first SKILL.md from the template above, or use our free Cursor skill generator: describe the task, the trigger conditions, the globs, and any tool permissions, and it produces a complete, lint-clean SKILL.md with the frontmatter and structure covered here. It's free, runs in the browser, and doesn't require a signup. Generate one, drop it in .cursor/skills/, and test it with /skill-name.