How to write custom Skills

Up to date as of April 2026.


What are Skills

Skills are how I “teach” Claude Code repeatable workflows without bloating CLAUDE.md.

A skill is a directory with a SKILL.md file (YAML frontmatter + Markdown instructions). Claude can load it automatically when it’s relevant, or you can run it directly as a /slash-command.

The practical reason to use them is simple: CLAUDE.md should contain the rules of the road, while skills should contain reusable moves.

The key differences:

  • CLAUDE.md / rules are “always-on” project memory. Great for stable constraints (commands, conventions, architecture).
  • Skills are “on-demand” playbooks. Great for actions and workflows (deploy, commit, review) or optional reference material.
  • .claude/commands/ still works, but custom commands were merged into skills. A .claude/commands/deploy.md and .claude/skills/deploy/SKILL.md both create /deploy. Skills win on flexibility because they can include supporting files.

Where to store them

LocationScope
~/.claude/skills/<skill-name>/SKILL.mdAll your projects (personal)
.claude/skills/<skill-name>/SKILL.mdThis project only (in git)
Plugin: <plugin>/skills/<skill-name>/SKILL.mdWhere the plugin is enabled

On name conflicts: enterprise > personal > project. Plugin skills use a plugin-name:skill-name namespace, so they don’t collide.

If you’re working alone, start with personal skills. If a workflow clearly belongs to one repo, move it into .claude/skills/ and commit it.


Minimal skill

This is the smallest useful example I know. It does one thing, has a clear trigger, and is easy to test.

mkdir -p ~/.claude/skills/explain-code

~/.claude/skills/explain-code/SKILL.md:

---
name: explain-code
description: Explains code with visual diagrams and analogies. Use when explaining how code works, teaching about a codebase, or when the user asks "how does this work?"
---

When explaining code, always:

1. **Start with an analogy** — compare to something from everyday life
2. **Draw a diagram** — ASCII art for structure or flow
3. **Walk through step-by-step** — what happens at each stage
4. **Highlight a gotcha** — common mistake or misconception

Test it two ways:

  • Let Claude pick it up automatically: “How does this code work?”
  • Invoke explicitly: /explain-code src/auth/login.ts

SKILL.md structure

---
name: skill-name            # becomes /skill-name
description: What it does and when to use. Claude reads this to decide auto-invocation.
---

# Content

Instructions for Claude on what to do when the skill is activated.

All frontmatter fields

You do not need most of these most of the time. For many real skills, name + description + instructions is enough.

FieldDescription
nameDisplay name. If omitted, uses the directory name. Lowercase letters, numbers, and hyphens only. Max 64 chars.
descriptionWhat it does and when to use it. Recommended. Descriptions longer than ~250 chars are truncated in the listing, so front-load the use case.
argument-hintAutocomplete hint for expected arguments. Example: "[issue-number]"
disable-model-invocationtrue to prevent Claude from auto-loading it. You run it manually as /name.
user-invocablefalse to hide it from the / menu (Claude can still use it).
allowed-toolsTools Claude can use without asking permission while the skill is active. Accepts a space-separated string or a YAML list.
modelOverride the model when the skill is active.
effortOverride effort level while the skill is active (low/medium/high/max, where max is Opus 4.6 only).
contextfork to run the skill in a forked subagent context.
agentWhich subagent type to use when context: fork is set.
hooksHooks scoped to this skill’s lifecycle.
pathsGlob patterns limiting when Claude auto-loads the skill (same format as path-scoped rules).
shellShell for inline ! command blocks (bash or powershell, Windows-only toggle required).

Three types of content

I think about skills in three buckets:

1. Reference — knowledge Claude applies

Loaded inline, Claude uses it alongside the conversation:

---
name: api-conventions
description: API design patterns for this codebase
---

When writing API endpoints:
- RESTful naming conventions
- Consistent error format: { error, code, message }
- Input validation on all endpoints
- JSDoc comments for all public routes

2. Task — step-by-step instruction for a specific action

For actions you want to control yourself, add disable-model-invocation: true:

---
name: deploy
description: Deploy the application to production
disable-model-invocation: true
context: fork
---

Deploy to production:
1. Run test suite — `bun test`
2. Build — `bun run build`
3. Push to deployment target
4. Verify deployment succeeded

3. Context — background knowledge for Claude only

Hidden from menu, Claude uses when needed:

---
name: legacy-system-context
description: Context about the legacy billing system. Load when working with src/billing/legacy/
user-invocable: false
paths:
  - "src/billing/legacy/**"
---

# Legacy Billing System

This system was written in 2015 and uses...
Never touch legacy-core.js directly...

Arguments

Arguments are where skills stop being static templates and start feeling like tools.

---
name: fix-issue
description: Fix a GitHub issue by number
disable-model-invocation: true
argument-hint: "[issue-number]"
---

Fix GitHub issue #$ARGUMENTS:

1. Read the issue description
2. Find relevant code
3. Implement the fix
4. Write tests
5. Create a commit

Invocation: /fix-issue 123

Positional arguments:

---
name: migrate-component
---

Migrate the $0 component from $1 to $2.
# /migrate-component SearchBar React Vue
# $0 = SearchBar, $1 = React, $2 = Vue

All arguments: $ARGUMENTS — everything after the command name.


Dynamic content via bash

The ! prefix executes a command and inserts the result before Claude sees the prompt:

---
name: pr-summary
description: Summarize the current pull request
allowed-tools: Bash(gh *)
context: fork
agent: Explore
---

## Pull Request Context
- Diff: !`gh pr diff`
- Comments: !`gh pr view --comments`
- Changed files: !`gh pr diff --name-only`

## Task
Summarize this pull request: what changed, why, and what to review carefully.

Commands execute before sending to Claude, which is why this pattern is useful for things like PR review, release notes, or incident summaries.


Path-specific skills

Load only when Claude is working with matching files:

---
name: react-patterns
description: React component patterns for this codebase
paths:
  - "src/components/**/*.tsx"
  - "src/pages/**/*.tsx"
---

When writing React components:
- Functional components only, no class components
- Custom hooks in src/hooks/
- Styles via CSS modules

Running in a subagent (context: fork)

This is one of the highest-leverage features. The skill executes in an isolated context, so the main conversation stays cleaner:

---
name: deep-research
description: Research a topic thoroughly in the codebase
context: fork
agent: Explore
---

Research $ARGUMENTS thoroughly:

1. Find relevant files using Glob and Grep
2. Read and analyze the code
3. Return a summary with specific file references

Pick agent based on what you want (read-only research vs implementation), or point at a custom subagent from .claude/agents/.


Folder with additional files

If a skill starts turning into a wall of Markdown, break it up. SKILL.md should stay readable:

.claude/skills/deploy/
├── SKILL.md              # main instructions + navigation
├── checklist.md          # detailed checklist
├── rollback-guide.md     # instructions in case of problems
└── scripts/
    └── health-check.sh   # verification script

In SKILL.md reference files explicitly:

---
name: deploy
description: Deploy to production with full checklist
---

Follow the deployment checklist in [checklist.md](checklist.md).
If something goes wrong, see [rollback-guide.md](rollback-guide.md).

After deployment run: !`./scripts/health-check.sh`

Keep SKILL.md under 500 lines. Details go in separate files.


Invocation control

This is the part worth getting right early, because it changes how “pushy” the skill feels in everyday use.

FrontmatterYou invokeClaude invokesIn context
(default)yesyesDescription always, content on invocation
disable-model-invocation: trueyesnoNot loaded automatically
user-invocable: falsenoyesDescription in context, hidden from menu

When to use what:

  • /deploy, /commit, /send-messagedisable-model-invocation: true (you control when)
  • Background knowledge about a legacy system → user-invocable: false (Claude decides when needed)

Built-in skills (included out of the box)

SkillWhat it does
/batch <instruction>Parallel refactoring — splits into tasks, runs agents in worktrees
/simplify [focus]Code review + fixes — 3 agents in parallel
/debug [description]Enables debug logging and analyzes the problem
/loop [interval] <prompt>Runs a prompt on a schedule
/claude-apiLoads Claude API reference

Practical examples

The best skills are usually boring operational tools you reach for repeatedly, not clever demos.

Commit helper

---
name: commit
description: Create a well-formatted git commit
disable-model-invocation: true
allowed-tools: Bash(git *)
---

Create a git commit:
1. Run `git diff --staged` to see changes
2. Write commit message in format: `type(scope): description`
   Types: feat, fix, docs, style, refactor, test, chore
3. Keep subject under 72 characters
4. Add body if changes are complex

$ARGUMENTS

Code review

---
name: review
description: Review code changes for quality, security, and best practices
context: fork
agent: Explore
allowed-tools: Read, Grep, Glob, Bash(git *)
---

Review the recent changes:

## Changes to review
!`git diff HEAD~1`

## Review checklist
- [ ] Code quality and readability
- [ ] Security issues (injections, exposed secrets)
- [ ] Error handling
- [ ] Test coverage
- [ ] Performance implications

Provide feedback organized by: Critical -> Warnings -> Suggestions

Session logger

---
name: session-log
description: Log this session's activity
disable-model-invocation: true
---

Append a summary of this session to logs/${CLAUDE_SESSION_ID}.log:

- What was accomplished
- Files changed
- Decisions made
- Next steps

$ARGUMENTS

Troubleshooting

When a skill feels flaky, the problem is usually one of three things: vague description, wrong invocation mode, or too much stuffed into one file.

Skill doesn’t trigger automatically:

  • Check that description contains keywords you use
  • Ask Claude “what skills are available?” — verify the skill is listed
  • Invoke manually with /skill-name to make sure it works

Skill triggers too often:

  • Make description more specific
  • Add disable-model-invocation: true

Description gets truncated:

  • Front-load the essentials — the first 250 characters matter most