Building Your Own Skill From Scratch
Skills turn repeated prompts into one-line commands you can invoke any time. This step-by-step guide walks you through writing, wiring, and iterating a Claude Code skill for any workflow you repeat.
TL;DR: A skill is a single SKILL.md file that turns a procedure you keep repeating into a command like /post-update or /file-issue. You write the instructions once, Claude follows them every time. This guide walks you from zero to a working skill, then shows you how to level it up.
What a skill actually is
A skill is a folder with one required file inside it: SKILL.md. That file has two parts - YAML frontmatter at the top (between --- markers) that tells Claude when to use the skill, and a markdown body that tells it what to do.
Claude Code skills follow the Agent Skills open standard, which means the core SKILL.md format is portable across other compatible tools including Gemini CLI, Cursor, GitHub Copilot, and more. Note that Claude Code-specific frontmatter fields - context: fork, disable-model-invocation, and dynamic context injection - are Claude Code extensions and may not work in other tools. Anthropic originally developed the format and released it as an open standard.
When Claude starts a session, it reads only the name and description of every available skill - just enough to know what's on offer. The full instructions only load when you invoke the skill. This means you can keep dozens of skills around without bloating your context on every turn.
When to build a skill
The signal is repetition. Build a skill when you:
- Keep pasting the same multi-step instructions into chat
- Have a section of
CLAUDE.mdthat grew into a procedure rather than a fact - Run the same sequence every time you do X (ship a post, pull analytics, file a bug, cut a release)
- Want a teammate (or a future you) to be able to run the same workflow reliably
Skills are not for one-off tasks. They're for workflows you run repeatedly and want to be consistent.
Your first skill in five minutes
This example creates a skill that summarizes your uncommitted git changes and flags anything risky. It's lifted directly from the official Claude Code skills documentation.
Step 1 - Create the skill directory
Personal skills live under ~/.claude/skills/ and are available in every project you open.
mkdir -p ~/.claude/skills/summarize-changes
Step 2 - Write SKILL.md
Save this to ~/.claude/skills/summarize-changes/SKILL.md:
---
description: Summarizes uncommitted changes and flags anything risky. Use when
the user asks what changed, wants a commit message, or asks to review their diff.
---
## Current changes
!`git diff HEAD`
## Instructions
Summarize the changes above in two or three bullet points, then list any risks
you notice such as missing error handling, hardcoded values, or tests that need
updating. If the diff is empty, say there are no uncommitted changes.
The line starting with !`git diff HEAD` is dynamic context injection - Claude Code runs that command first and replaces the line with the actual output before Claude ever sees the skill. Your instructions arrive with the real diff already baked in.
Step 3 - Test it
Open Claude Code in any git project, make a small edit, and try either of these:
# Invoke directly
/summarize-changes
# Or just ask naturally - Claude picks it up from the description
What did I change?
Claude should respond with a short summary and a list of risks. That's it - you built a working skill.
Where skills live (and who can use them)
The location of your SKILL.md controls who has access:
- Personal -
~/.claude/skills/<skill-name>/SKILL.md- all your projects - Project -
.claude/skills/<skill-name>/SKILL.md- this repo only; commit it so teammates get it - Plugin -
<plugin>/skills/<skill-name>/SKILL.md- wherever the plugin is enabled - Enterprise - managed settings; pushed to all users in your organization
The command you type comes from the directory name, not the frontmatter. A skill at .claude/skills/post-update/SKILL.md becomes /post-update. The optional name frontmatter field only sets the display label shown in skill listings - it does not change the slash command.
Claude Code watches skill directories for changes. Add or edit a skill file and it's live in the current session without restarting.
A real example - posting a social update
Say you post a project update to social media every time you ship a feature. The workflow is always the same: pull the latest changes, draft copy, cross-post. Here's a skill for it. Create the directory at .claude/skills/post-update/ and save this as SKILL.md:
---
description: Draft and post a social update about the latest shipped work. Use
when the user says "post an update", "tweet this", or "share what shipped".
disable-model-invocation: true
allowed-tools: Bash(gh *) Bash(git log *)
---
## What just shipped
!`git log --oneline -10`
## Instructions
1. Read the commit messages above and pick the most user-facing change.
2. Draft a short social post (under 280 characters) that explains what changed
and why it matters to someone using this product. No jargon. No hashtags.
3. Show the draft to the user and ask for approval before posting.
4. If approved, use the gh CLI or the appropriate tool to post it.
$ARGUMENTS
A few things to notice:
disable-model-invocation: truemeans Claude will never run this automatically. You type/post-updatewhen you're ready. You don't want it firing because it thinks your code looks ready to ship.allowed-toolspre-approves specific Bash patterns so Claude doesn't prompt for permission on everygitorghcall while the skill is active.$ARGUMENTSis a placeholder for anything you add after the skill name. Run/post-update staging onlyand Claude sees that note inline.
Frontmatter fields that matter most
The full frontmatter reference is in the official docs. Here are the ones you'll reach for most:
description- Recommended. This is how Claude decides when to activate the skill automatically. Put the key use case first. The combineddescriptionandwhen_to_usetext is truncated at 1,536 characters in the skill listing.disable-model-invocation: true- You trigger it manually with/name. Claude never runs it on its own, and the description is removed from Claude's context entirely. Use for anything with side effects: deploy, post, send, commit.user-invocable: false- Claude loads it automatically when relevant, but it's not in the/menu. Use for background knowledge that should inform Claude silently.allowed-tools- Tools Claude can use without prompting you while the skill runs. Example:Bash(git *) Read Grep.context: fork- Runs the skill in an isolated subagent with a fresh context window. Results are summarized and returned to your main conversation. Good for heavy research or analysis that would otherwise crowd your main context.argument-hint- Shows a hint during autocomplete. Example:[issue-number]or[filename] [format].
Dynamic context and arguments
Two patterns make skills dramatically more useful than static prompts.
Dynamic context injection
Prefix a shell command with ! and it runs before Claude sees anything. The output replaces the line in the skill content.
---
description: Triage open GitHub issues. Use when asked to review issues.
context: fork
agent: Explore
---
## Open issues
!`gh issue list --state open --limit 20 --json number,title,labels`
## Instructions
Group these issues by theme. Flag anything that looks like a regression.
Suggest which three to fix first and why.
For multi-line commands, use a fenced block opened with ```!:
## Environment
```!
node --version
git status --short
cat package.json | grep '"version"'
```
This is preprocessing - it runs once, before Claude sees the skill. Claude receives the final rendered text, not the commands.
Arguments
Use $ARGUMENTS for a single freeform input, or $0, $1, $2 for positional arguments (0-based index, so $0 is the first argument):
---
description: Fix a GitHub issue by number. Use when asked to fix a specific issue.
disable-model-invocation: true
---
Fix GitHub issue $ARGUMENTS following our coding standards.
1. Read the issue description with `gh issue view $ARGUMENTS`
2. Understand the requirements
3. Implement the fix
4. Write tests
5. Create a commit
Save this in a directory named fix-issue (e.g. .claude/skills/fix-issue/SKILL.md) - the directory name becomes the slash command /fix-issue. Run /fix-issue 42 and Claude receives "Fix GitHub issue 42 following our coding standards..." with the issue number substituted inline.
Adding supporting files
A skill directory can hold anything. Scripts, templates, reference docs. This keeps your SKILL.md focused on the essentials while letting Claude load detail on demand:
my-skill/
├── SKILL.md - main instructions (required)
├── reference.md - detailed API docs, loaded when needed
├── examples.md - sample outputs showing expected format
└── scripts/
└── validate.sh - script Claude can execute
Reference these files from your SKILL.md body so Claude knows what each contains and when to read it:
## Additional resources
- For complete API details, see [reference.md](reference.md)
- For usage examples, see [examples.md](examples.md)
The official guidance is to keep SKILL.md under 500 lines and push detailed reference material to separate files. The skill body loads into context every time it's active - every extra line is a recurring token cost.
Running a skill in a subagent
Add context: fork to run a skill in an isolated subagent with its own fresh context window. The skill content becomes the task that drives the subagent. Results come back as a summary.
---
description: Research a topic thoroughly across the codebase.
context: fork
agent: Explore
---
Research $ARGUMENTS thoroughly:
1. Find relevant files using Glob and Grep
2. Read and analyze the code
3. Summarize findings with specific file references
The agent field picks which subagent runs the work. Explore is read-only and keeps context small. Plan is for architecture research. general-purpose has full tool access. You can also point to a custom agent from .claude/agents/. If the field is omitted, general-purpose is used.
Key takeaways
- A skill is a directory with a
SKILL.mdfile; the directory name becomes the/command. - Skill body content only loads when the skill is invoked - keep it under 500 lines and push reference detail to separate files.
- Use
disable-model-invocation: truefor anything with side effects (deploy, post, commit) so Claude can't trigger it automatically. - Dynamic context injection (
!`command`) runs shell commands before Claude sees the skill, grounding instructions in live data. $ARGUMENTSand positional$0/$1/$2substitutions let one skill handle many inputs.context: forkisolates heavy or risky work in a subagent so it doesn't pollute your main conversation context.- The base Agent Skills format is portable across Gemini CLI, Cursor, GitHub Copilot, and more; Claude Code-specific fields are extensions that other tools may not support.
Try this next: Once you have a skill working, learn how to evaluate whether it's actually triggering on the right prompts and producing consistent output - see the Iterating on Skills guide for the eval loop and the skill-creator plugin that automates it.