skills

SKILL.md Format Guide: Writing Effective Claude Code Skills

Quick Answer

A SKILL.md file has 5 core sections: title (skill name), description (when Claude should use this skill), context (@imported files + project facts), instructions (numbered steps), and examples (input/output pairs). The description and instructions are mandatory — everything else is optional but strongly recommended.

Skill files are read by Claude as part of session context. The format is designed to be readable both by Claude and by humans on your team. There's no proprietary DSL — pure Markdown with a few conventions about section naming.

The H1 title sets the skill name. Use verb-noun format: 'Write Migration', 'Review Code', 'Generate API Docs'. This name is how you'll reference the skill in prompts and how Claude identifies it when deciding which skill to apply.

The Description section is critical for proactive invocation. Claude reads descriptions across all your skills to match them against your current task. Write the description as: 'Use this skill when [condition]. Applies to: [specific cases].' Overly broad descriptions cause false positives (wrong skill applied); overly narrow descriptions cause misses.

The Context section is where you put project-specific facts that the skill needs. This is different from CLAUDE.md: CLAUDE.md has facts needed every session; the Context section has facts needed only for this type of task. Include @imports of relevant files (schema files, config files, example files) so Claude always has the current state.

The Instructions section is the core of the skill. Number every step. Be specific about file paths, command names, and expected outputs. Include conditional steps ('If the table already exists, modify it instead of creating new'). End with a verification step ('Run tests to confirm the change is correct').

The Examples section is optional but has high impact. Include 1-2 realistic input/output pairs. For code-generating skills, show actual code in fenced blocks. Claude learns from examples more reliably than from abstract rules — a good example is worth more than three bullet-point rules.

Examples

Complete SKILL.md with all sections annotatedmarkdown
# Write API Endpoint
# ^ H1: skill name — verb-noun, ≤5 words

## Description
# ^ Tell Claude WHEN to use this skill
Use this skill when asked to create a new REST API endpoint, route handler, or controller.
Applies to: Express/Fastify routes, Next.js API routes, tRPC procedures.
Do NOT use for: GraphQL resolvers (use write-resolver skill instead).

## Context
# ^ Project-specific facts + @imported current state
- API framework: Fastify 4.x
- Auth middleware: `src/middleware/auth.ts` — attach as preHandler
- Validation: Zod schemas in `src/schemas/`
- Error responses: use `reply.sendError()` from `src/utils/errors.ts`
- Route files: `src/routes/[resource].ts`

@src/utils/errors.ts
@src/middleware/auth.ts

## Instructions
# ^ Numbered steps — be explicit about files, commands, checks
1. Identify the resource and HTTP method from the request
2. Create or open `src/routes/[resource].ts`
3. Define a Zod schema for request body/params in `src/schemas/[resource].ts`
4. Write the route handler with:
   - Auth preHandler (unless public route — confirm with user)
   - Zod validation of request body/params
   - Business logic or service call
   - Typed response using `reply.send()`
5. Register the route in `src/app.ts`
6. Write a unit test in `src/routes/[resource].test.ts`
7. Run `pnpm test routes/[resource]` and `pnpm typecheck` to verify

## Rules
# ^ Explicit prohibitions — most important constraints
- NEVER skip auth middleware on non-public routes
- NEVER inline business logic — call a service or repository function
- NEVER return raw database errors — map them to HTTP responses
- ALWAYS include a test for at least the happy path and a 400 case

## Examples
# ^ Input/output pairs — show real code

### Example: GET /api/users/:id

Route file (`src/routes/users.ts`):
```typescript
import { FastifyPluginAsync } from 'fastify'
import { z } from 'zod'
import { getUserById } from '../services/users'
import { requireAuth } from '../middleware/auth'

const ParamsSchema = z.object({
  id: z.string().cuid(),
})

const usersRoutes: FastifyPluginAsync = async (fastify) => {
  fastify.get<{ Params: z.infer<typeof ParamsSchema> }>(
    '/users/:id',
    { preHandler: [requireAuth] },
    async (request, reply) => {
      const { id } = ParamsSchema.parse(request.params)
      const user = await getUserById(id)
      if (!user) return reply.sendError(404, 'User not found')
      return reply.send(user)
    }
  )
}

export default usersRoutes
```
Skill file validation checklistmarkdown
# Before committing a skill, verify:

## Description quality
- [ ] Reads like 'Use when [specific condition]'
- [ ] Has 'Do NOT use for' if there are common misapplications
- [ ] Specific enough that a different skill wouldn't match the same task

## Instructions quality  
- [ ] All steps are numbered
- [ ] File paths are explicit (not 'the route file' but 'src/routes/[resource].ts')
- [ ] Commands are exact (`pnpm test` not 'run tests')
- [ ] Includes a verification step
- [ ] Rules section has explicit prohibitions

## Examples quality
- [ ] At least one realistic example
- [ ] Code blocks use the actual format expected
- [ ] Example shows the output Claude should produce, not just the input

## Test the skill
- [ ] Invoked on a simple case and produced correct output
- [ ] Invoked on an edge case and handled it correctly
- [ ] Did NOT trigger on an unrelated task

Tips

  • Write the description by asking: if a teammate saw only the description, would they know exactly when to use this skill vs a different one?
  • The Rules section should contain things Claude regularly gets wrong without explicit prohibition — derive rules from actual mistakes, not from theoretical concerns.
  • Use @imports sparingly — each imported file consumes context tokens. Only import files that the skill genuinely needs to do its job correctly.
  • Include a verification command at the end of every code-changing skill — `pnpm test`, `pnpm typecheck`, `pnpm lint`. This makes the skill self-correcting.
  • If your instructions section has more than 10 steps, consider whether you're encoding one skill or two. Split complex skills and reference each other via @imports.
  • Date your examples in comments — examples become stale as APIs change. `// Example valid as of Fastify 4.x, 2026` helps future maintainers.

FAQ

Is the section naming (## Description, ## Instructions) required?+

The section names are conventional, not technically required. Claude reads the file as Markdown and understands the structure. However, consistent naming across your skills makes them easier to maintain and makes Claude's matching more reliable — it learns to look for 'Description' sections when assessing skill relevance.

Can I include images or diagrams in a skill file?+

Claude Code reads skill files as text — embedded images (markdown image syntax) will show as broken references. If you want to reference a diagram, link to it externally or describe it in text. Mermaid diagram syntax in a code block works — Claude can read and understand Mermaid.

How do I version skills when the underlying tools change?+

Add version annotations to examples: `# Example for Prisma 5.x`. When you upgrade a library, audit skill files for outdated commands or APIs. Git history on skill files is your version record — commit skill changes with the library upgrade commit for traceability.

Related Guides