Claude Code Skills Tutorial: Write Your First SKILL.md
Skills are the most leveraged thing you can add to a Claude Code setup, and they are just markdown files. This tutorial walks through what a skill actually is, where the files go, what the frontmatter fields do, and then builds a complete working skill you can copy into a project right now. Everything here is verified against the official Claude Code docs as of August 2026.
What a skill is
A skill is a folder containing a SKILL.md file. The file has optional YAML frontmatter at the top and markdown instructions below. That is the whole format. When Claude Code starts, it reads the name and description of every skill it can find. The full body of the skill loads only when the skill is actually used. The docs call this progressive disclosure: descriptions are always in context, bodies load on demand.
That design is why skills scale where a giant CLAUDE.md does not. You can have thirty skills installed and pay the context cost of thirty one-line descriptions, not thirty full documents.
A skill runs in two ways:
- You invoke it by typing
/skill-namein the session. - Claude invokes it on its own when your request matches the skill's description.
Both paths load the same instructions. The second one is what makes skills feel like installed behavior rather than saved prompts.
Where skill files live
| Location | Path | Applies to |
|---|---|---|
| Personal | ~/.claude/skills/<skill-name>/SKILL.md | Every project on your machine |
| Project | .claude/skills/<skill-name>/SKILL.md | The repo it lives in, for everyone who clones it |
| Plugin | <plugin>/skills/<skill-name>/SKILL.md | Wherever the plugin is enabled |
Project skills are the interesting ones for teams: they ride along in version control, so "how we write commit messages here" becomes something every teammate's Claude already knows. Personal skills are for your own habits. When names collide across levels, the more centrally managed location wins.
The skill's name comes from its directory name, so .claude/skills/release-notes/SKILL.md is invoked as /release-notes.
The frontmatter fields that matter
Frontmatter is optional, but in practice you always want description. Here are the fields you will actually use, starting with the ones to learn first:
description: what the skill does and when to use it. This is the single most important line in the file, because it is how Claude decides whether to invoke the skill on its own. Write it like a routing rule, not a slogan: say what the skill does and name the situations that should trigger it.disable-model-invocation: true: makes the skill manual-only. Claude will not load it by itself; only/skill-nameruns it. Use this for anything with side effects you want a human to initiate, like deploys.user-invocable: false: the opposite. Hides the skill from the/menu so only Claude can trigger it. Good for background conventions that a human would never invoke by name.allowed-tools: a list of tools Claude may use without asking for permission during the turn the skill is invoked. The grant clears on the next user message, so it is a scoped convenience, not a standing permission.argument-hint: autocomplete hint text, like[issue-number], shown when someone types the slash command.context: fork: runs the skill in a forked subagent context instead of the main conversation, which keeps big skill workloads from flooding your main context window.modelandeffort: pin a specific model or effort level for the skill, overriding the session's settings.
One documented constraint worth knowing: the combined description plus when_to_use text gets truncated at 1,536 characters in skill listings, so keep descriptions tight.
A complete working example
Let's build a real skill: release notes drafted from git history. This is a task with a stable procedure, an obvious trigger phrase, and dynamic input, which makes it a perfect skill candidate.
Create the folder and file in your repo:
your-repo/
.claude/
skills/
release-notes/
SKILL.md
And here is the complete SKILL.md:
---
description: Drafts release notes from commits since the last git tag.
Use when the user asks for release notes, a changelog entry, or a
summary of what shipped since the last release.
---
## Commits since the last tag
!`git log $(git describe --tags --abbrev=0)..HEAD --oneline --no-merges`
## Instructions
Draft release notes from the commits above:
1. Group changes under "Added", "Changed", and "Fixed" headings.
Omit any heading with no entries.
2. Rewrite each commit message as a user-facing sentence. Drop pure
chore commits (dependency bumps, CI tweaks) unless they change
behavior users can see.
3. Keep each bullet to one line. No marketing language.
4. If there are no commits since the last tag, say so and stop.
Output the notes as markdown, ready to paste into CHANGELOG.md.
Do not commit anything.
Two things in this file are worth pausing on.
First, the !`command` line. That is dynamic context injection: Claude Code runs the shell command when the skill is invoked and inserts its output into the skill content before Claude sees it. The model never has to decide to run git log; the data is just there. This one feature turns skills from static prompts into small programs.
Second, the description names the trigger phrases. "Release notes", "changelog entry", "what shipped". When you later type "write me a changelog entry for this release", Claude matches that against the description and loads the skill without being told.
Try it: open Claude Code in a repo that has at least one git tag and type /release-notes, or just ask for release notes in plain language.
Supporting files
A skill folder can hold more than SKILL.md: templates, reference docs, scripts. Reference them from the skill body so Claude knows what each file is for and when to read it. The docs provide a ${CLAUDE_SKILL_DIR} variable that resolves to the skill's own directory wherever it is installed, which is how you point at bundled files without hardcoding paths:
Use the template at ${CLAUDE_SKILL_DIR}/template.md as the output format.
The official guidance is to keep SKILL.md under 500 lines and push detailed reference material into separate files that load only when needed. Same progressive-disclosure idea, one level down.
Managing the skills you have
Type /skills to see every skill Claude can currently find, toggle visibility states, and save the result to .claude/settings.local.json. This is the quickest way to confirm a new skill was discovered, and to quiet a noisy one without deleting it.
What makes a good skill
After building a few dozen of these, the pattern that holds up:
- One job per skill. "Review my diff" and "write release notes" are two skills, not one "git helper".
- The description is a routing rule. If Claude is not invoking your skill when it should, the fix is almost always a more literal description that names the user phrasings, not a longer body.
- Inject data instead of describing where to find it. A
!`command`line beats a paragraph telling Claude which command to run. - End with output rules. Say exactly what the deliverable looks like and what the skill must not do. "Do not commit anything" has saved us more than once.
FAQ
What is the difference between a skill and CLAUDE.md?
CLAUDE.md is always in context: it should hold standing facts like build commands and conventions. A skill loads on demand: it should hold multi-step procedures. If an instruction only matters during a specific task, it belongs in a skill, and your CLAUDE.md stays short. We cover the split in our CLAUDE.md best practices article.
Do I need frontmatter at all?
No. A bare markdown file works, and the skill name falls back to the directory name. But without a description, Claude has little to match against, so automatic invocation will be unreliable. Treat description as required.
Can a skill run without me typing the slash command?
Yes, that is the default. Claude invokes any skill whose description matches the task, unless the skill sets disable-model-invocation: true.
Where should team skills go?
In the repo, under .claude/skills/, committed to version control. Everyone who clones the repo gets them. Personal habits go in ~/.claude/skills/ instead.