How to Build Your Own Claude Code Skills - The Complete Guide
A practical, in-depth guide to building custom Claude Code skills. Covers SKILL.md format, frontmatter, triggers, arguments, subagents, dynamic context injection, and real-world examples.
If you use Claude Code regularly, you have probably built up a set of repeatable workflows. Maybe you write commit messages the same way every time. Maybe you have a specific process for reviewing pull requests, deploying to staging, or researching a new library before adopting it.
Every time you explain that process to Claude from scratch, you are wasting context and time. Skills solve this. They are reusable instruction files that teach Claude how to execute your workflows — consistently, every session, without you repeating yourself.
This guide covers everything you need to build your own Claude Code skills, from the basics to advanced patterns like subagent execution and dynamic context injection. If you have built slash commands before, skills are the evolution of that concept — and they are significantly more powerful.
What Is a Claude Code Skill?
A skill is a markdown file called SKILL.md that lives in a specific directory. It contains two things: YAML frontmatter that tells Claude when and how to use the skill, and markdown instructions that Claude follows when the skill is invoked.
Skills are not plugins. They are not extensions with runtime capabilities. They are instruction files. They cannot run code independently — they instruct Claude to use its existing tools (Read, Write, Bash, Grep, and so on) in a specific way, following a specific workflow.
Custom commands (.claude/commands/*.md) and skills (.claude/skills/*/SKILL.md) have been merged. Both create the same /slash-command interface. Your existing command files keep working, but skills add supporting files, frontmatter control, and auto-discovery.
The Minimal Skill
Here is the absolute minimum you need:
my-skill/
└── SKILL.md---
name: my-skill
description: What this skill does and when to use it.
---
# My Skill
Instructions for Claude go here.The name field becomes the /slash-command. The description helps Claude decide when to load the skill automatically. The markdown body is what Claude actually reads and follows.
Where Skills Live
Where you store a skill determines who can use it. There are three main locations:
| Scope | Path | Who Gets It |
|---|---|---|
| Personal | ~/.claude/skills/<skill-name>/SKILL.md | You, across all projects |
| Project | .claude/skills/<skill-name>/SKILL.md | Anyone who clones the repo |
| Plugin | <plugin>/skills/<skill-name>/SKILL.md | Where the plugin is enabled |
Personal skills are great for your own workflows — commit messages, code review checklists, research processes. Project skills are great for team conventions — deploy procedures, PR templates, architecture guidelines. Commit your .claude/skills/ directory to version control and your entire team gets the same workflows.
When skills share the same name across levels, higher-priority locations win: enterprise > personal > project. Plugin skills use a namespace (plugin-name:skill-name), so they never conflict.
Monorepo Support
Claude Code automatically discovers skills from nested .claude/skills/ directories. If you are editing a file in packages/frontend/, Claude also looks for skills in packages/frontend/.claude/skills/. This means each package in a monorepo can have its own skills.
The Frontmatter — Configuring Skill Behaviour
The YAML frontmatter between the --- markers controls how your skill behaves. Every field is optional except description, which is strongly recommended.
Here is a reference of every available field:
| Field | What It Does |
|---|---|
| name | Display name and /slash-command. Defaults to directory name. Lowercase, hyphens, max 64 chars. |
| description | What the skill does and when to use it. Claude uses this to decide when to auto-load. Max 250 chars shown in listing. |
| argument-hint | Hint shown during autocomplete. Example: [issue-number] or [filename] [format]. |
| disable-model-invocation | Set true to prevent Claude from auto-loading. User must invoke manually with /name. |
| user-invocable | Set false to hide from the / menu. Only Claude can invoke it. |
| allowed-tools | Restrict which tools Claude can use when the skill is active. |
| model | Override the model used when the skill runs. |
| effort | Override effort level: low, medium, high, or max. |
| context | Set to fork to run in an isolated subagent context. |
| agent | Which subagent type to use with context: fork (Explore, Plan, general-purpose, or custom). |
| paths | Glob patterns that limit when the skill auto-activates based on which files you are working with. |
| hooks | Hooks scoped to this skill lifecycle. |
| shell | Shell for inline commands: bash (default) or powershell. |
Controlling Who Can Invoke a Skill
By default, both you and Claude can invoke any skill. You type /skill-name, or Claude loads it automatically when relevant. Two frontmatter fields change this:
disable-model-invocation: true — Only you can invoke the skill. Use this for workflows with side effects: deployments, sending messages, deleting things. You do not want Claude deciding to deploy because your code looks ready.
user-invocable: false — Only Claude can invoke the skill. Use this for background knowledge that is not actionable as a command. A legacy-system-context skill explains how an old system works. Claude should know this when relevant, but /legacy-system-context is not a meaningful action for users.
Here is how these settings affect behaviour:
| Setting | You Can Invoke | Claude Can Invoke | Description in Context |
|---|---|---|---|
| Default | Yes | Yes | Always loaded |
| disable-model-invocation: true | Yes | No | Not loaded — hidden from Claude |
| user-invocable: false | No | Yes | Always loaded |
Writing Good Descriptions
The description is the single most important field. It determines whether Claude loads your skill at the right time. A vague description means your skill never triggers. An overly broad description means it triggers when you do not want it.
Weak vs Strong Descriptions
Here is a weak description:
description: Generates commit messages.Here is a strong description:
description: Generates structured commit messages following the Conventional
Commits standard. Use when you want to commit your changes and need a
well-formatted message. Triggers on "write a commit message", "commit my
changes", "summarize my staged diff", "what should my commit say", or any
request to describe staged changes for version control.The strong version does three things the weak one does not: it specifies the output format (Conventional Commits), it describes the use case (committing changes), and it lists explicit trigger phrases. If your skill is not triggering when you expect, the fix is almost always adding more trigger phrases to the description.
Descriptions longer than 250 characters get truncated in the skill listing. Front-load the key use case in the first sentence.
Writing Effective Instructions
The markdown body after the frontmatter is what Claude actually follows. Good instructions share a few principles.
Principle 1: Generate First, Clarify Second
Skills should produce output immediately. Make assumptions and flag them rather than asking questions. If Claude asks "what format do you want?" every time you invoke a commit message skill, the skill is not doing its job.
Principle 2: Define the Output Format Explicitly
Specify exact structure, required fields, and character limits. If you want a commit message in Conventional Commits format, show Claude exactly what that looks like — do not just say "write a good commit message."
Principle 3: Include Counterexamples
Show Claude what not to do. This is especially effective when output format drifts over time:
## Common mistakes to avoid
Wrong: "Updated the authentication flow"
Right: "refactor(auth): simplify token validation logic"
Wrong: "Fixed bugs"
Right: "fix(api): handle null response from upstream service"Principle 4: Be Directive, Not Conversational
Skills are instructions, not conversations. Use imperative verbs: "Read the staged diff", "Generate a commit message", "Run the test suite." Do not write "You might want to consider checking the staged diff." Tell Claude what to do.
Passing Arguments to Skills
Skills accept arguments via the $ARGUMENTS placeholder. When you run /fix-issue 123, $ARGUMENTS gets replaced with 123.
---
name: fix-issue
description: Fix a GitHub issue by number
disable-model-invocation: true
argument-hint: <issue-number>
---
Fix GitHub issue $ARGUMENTS following our coding standards.
1. Read the issue description
2. Understand the requirements
3. Implement the fix
4. Write tests
5. Create a commitFor skills that take multiple arguments, use positional access with $ARGUMENTS[N] or the shorthand $N:
---
name: migrate-component
description: Migrate a component from one framework to another
---
Migrate the $0 component from $1 to $2.
Preserve all existing behaviour and tests.Running /migrate-component SearchBar React Vue replaces $0 with SearchBar, $1 with React, and $2 with Vue.
If your skill does not include $ARGUMENTS anywhere in the content but arguments are passed, Claude Code appends ARGUMENTS: <your input> to the end automatically. So arguments are never lost.
Full Example: Commit Message Writer
Let us build a complete, production-quality skill from scratch. This one generates commit messages following the Conventional Commits standard.
Step 1: Create the Directory
mkdir -p ~/.claude/skills/commit-message-writerOn Windows PowerShell:
New-Item -ItemType Directory -Force -Path "$HOME\.claude\skills\commit-message-writer"Step 2: Write the SKILL.md
---
name: commit-message-writer
description: Generates structured commit messages following the Conventional Commits
standard. Use when you want to commit your changes and need a well-formatted
message. Triggers on "write a commit message", "commit my changes", "summarize
my staged diff", "what should my commit say", or any request to describe staged
changes for version control.
---
# commit-message-writer
You generate structured commit messages from staged git changes.
## How to invoke
Run `git diff --staged` to read the staged changes. If nothing is staged,
tell the user and suggest they run `git add` first.
Generate first. Do not ask clarifying questions before producing the commit
message. If you need to make assumptions about scope or type, make them and
note them after the output.
## Output format
```
type(scope): short description
[body — optional, include if changes are non-trivial]
[footer — optional]
```
**Type** — choose one:
- `feat` — a new feature
- `fix` — a bug fix
- `docs` — documentation changes only
- `refactor` — code change that neither fixes a bug nor adds a feature
- `test` — adding or updating tests
- `chore` — build process, tooling, or dependency updates
**Scope** — the module, file, or area affected. Use the directory name or
component name. Omit if the change spans the entire codebase.
**Short description** — imperative mood, under 72 characters, no period.
"Add user authentication" not "Added user authentication."
**Body** — what changed and why, not how. One bullet per logical change.
Skip if the short description is self-explanatory.
**Footer** — include `BREAKING CHANGE:` if backward compatibility breaks.
Include `Closes #N` if it resolves a GitHub issue.
## Quality rules
- Never use "updated", "changed", or "modified" — be specific
- Never write "various improvements" or "misc fixes" — name what improved
- If more than three files changed across unrelated concerns, flag it:
"These changes may be better split into separate commits"
- Short description must be under 72 characters — count before outputting
## Example output
Input: staged changes adding a rate limiter to an API endpoint
```
feat(api): add rate limiting to /query endpoint
- Limits requests to 100 per minute per IP using token bucket algorithm
- Returns 429 with Retry-After header when limit exceeded
- Adds rate limit configuration to environment variables
Closes #47
```Step 3: Test It
Open Claude Code in any git repository with staged changes. Test it three ways:
- Direct invocation: type /commit-message-writer
- Natural language: "write a commit message for my staged changes"
- Edge case: stage nothing and verify the skill tells you to run git add first
Adding Supporting Files
Skills can include multiple files in their directory. This keeps SKILL.md focused while giving Claude access to detailed reference material when needed.
my-skill/
├── SKILL.md # Main instructions (required)
├── reference.md # Detailed API docs
├── examples/
│ └── sample.md # Example outputs
└── scripts/
└── validate.sh # Scripts Claude can executeReference supporting files from your SKILL.md so Claude knows they exist:
## Additional resources
- For complete API details, see [reference.md](reference.md)
- For usage examples, see [examples.md](examples.md)Keep SKILL.md under 500 lines. Move detailed reference material, long examples, and API documentation to separate files. Claude loads them only when needed, keeping context usage efficient.
Advanced: Dynamic Context Injection
This is one of the most powerful skill features. The !`command` syntax runs shell commands before Claude sees the skill content. The command output replaces the placeholder, so Claude receives real data — not a template.
---
name: pr-summary
description: Summarise changes in a pull request
context: fork
agent: Explore
allowed-tools: Bash(gh *)
---
## Pull request context
- PR diff: !`gh pr diff`
- PR comments: !`gh pr view --comments`
- Changed files: !`gh pr diff --name-only`
## Your task
Summarise this pull request. Focus on what changed and why.
Highlight any breaking changes or areas that need careful review.When this skill runs, each !`command` executes immediately — before Claude processes anything. Claude only sees the fully rendered prompt with actual PR data baked in. This is preprocessing, not tool invocation.
This pattern is incredibly useful for skills that need live data: git status, API responses, environment information, CI pipeline results.
Advanced: Running Skills in Subagents
Add context: fork to your frontmatter when you want a skill to run in isolation. The skill content becomes the prompt that drives a subagent. It will not have access to your conversation history — it runs in its own context window.
---
name: deep-research
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 analyse the code
3. Summarise findings with specific file references
4. Identify potential issues or improvementsWhen this skill runs:
- A new isolated context is created
- The subagent receives the skill content as its task
- The agent field determines the execution environment (model, tools, permissions)
- Results are summarised and returned to your main conversation
The agent field accepts built-in types (Explore, Plan, general-purpose) or any custom subagent you have defined in .claude/agents/. If omitted, it defaults to general-purpose.
context: fork only makes sense for skills with explicit task instructions. If your skill just contains guidelines like "use these API conventions" without an actionable task, the subagent receives the guidelines but has nothing to do — and returns without meaningful output.
Advanced: Restricting Tool Access
Use allowed-tools to limit what Claude can do when a skill is active. This is useful for safety — creating read-only exploration skills, or preventing a research skill from modifying files:
---
name: safe-reader
description: Explore the codebase without making changes
allowed-tools: Read, Grep, Glob
---
Explore the codebase and answer the user's question.
Do not modify any files.Advanced: Generating Visual Output
Skills can bundle and run scripts in any language. One powerful pattern is generating interactive HTML files that open in your browser — codebase visualisations, dependency graphs, test coverage reports.
The pattern is simple: your SKILL.md tells Claude to run a script bundled in the skill directory. The script does the heavy lifting. Claude handles orchestration.
codebase-visualizer/
├── SKILL.md
└── scripts/
└── visualize.py---
name: codebase-visualizer
description: Generate an interactive collapsible tree visualisation of your
codebase. Use when exploring a new repo or understanding project structure.
allowed-tools: Bash(python *)
---
# Codebase Visualizer
Run the visualisation script from your project root:
```bash
python ~/.claude/skills/codebase-visualizer/scripts/visualize.py .
```
This creates codebase-map.html and opens it in your default browser.The script can be written in Python, Node, Bash — whatever makes sense. Because it is bundled with the skill, it is portable and self-contained. You can use the $CLAUDE_SKILL_DIR variable to reference the script location regardless of the current working directory.
String Substitutions Reference
Skills support several dynamic variables that get replaced at runtime:
| Variable | What It Does |
|---|---|
| $ARGUMENTS | All arguments passed when invoking the skill |
| $ARGUMENTS[N] or $N | Access a specific argument by position (0-based) |
| ${CLAUDE_SESSION_ID} | Current session ID — useful for logging or session-specific files |
| ${CLAUDE_SKILL_DIR} | Directory containing the SKILL.md file — use to reference bundled scripts |
Cross-Platform Compatibility
Claude Code skills follow the Agent Skills open standard. The same SKILL.md format works across multiple AI tools:
| Agent | Skills Directory |
|---|---|
| Claude Code | ~/.claude/skills/ |
| GitHub Copilot | ~/.copilot/skills/ or .github/skills/ |
| Cursor | ~/.cursor/skills/ |
| Gemini CLI | ~/.gemini/skills/ |
Write once, use across tools. The frontmatter format and markdown instructions are identical. Claude Code adds extra features like subagent execution and dynamic context injection, but the core standard is portable.
Choosing What to Build
The best skills share three properties:
- Repeatable workflow — you follow the same steps every time, even if the specifics vary
- Clear trigger — you can complete the sentence "I need this skill when I want to..."
- Consistent output format — produces results with a fixed structure (commit messages, PR descriptions, code reviews)
Good candidates for skills:
- Commit messages — same format every time
- Pull request descriptions — consistent structure with summary, test plan, breaking changes
- Code review checklists — your team's standard review process encoded
- Changelog entries — structured output for version documentation
- Deploy workflows — step-by-step with safety checks
- Research workflows — fetch docs, explore codebase, write findings
Poor candidates:
- Open-ended requests like "help me think through this architecture"
- One-off tasks you will never repeat
- Tasks that require so much context they cannot be templated
Troubleshooting
Skill Not Triggering
- Check the description includes keywords users would naturally say
- Verify the skill appears when you ask "What skills are available?"
- Add more explicit trigger phrases to the description
- Try invoking directly with /skill-name to confirm it works at all
Skill Triggers Too Often
- Make the description more specific and narrow
- Add disable-model-invocation: true if you only want manual invocation
Descriptions Getting Cut Short
If you have many skills, descriptions are shortened to fit a character budget (1% of context window, minimum 8,000 characters). Front-load the key use case in the first sentence. You can raise the limit by setting the SLASH_COMMAND_TOOL_CHAR_BUDGET environment variable.
Improving Skills Over Time
Skills are not write-once-forget. They improve with use.
- When the skill does not trigger: add the phrase you used to the description
- When the output format drifts: add more explicit format rules and counterexamples
- When scope creeps: resist adding unrelated tasks — build a separate skill instead
- When context gets too large: move reference material to supporting files
Each skill should do one thing well. Three focused skills beat one "do everything" skill every time.
The Bottom Line
Claude Code skills are markdown files. That simplicity is the point. You do not need to learn a framework, install dependencies, or write runtime code. You write instructions in a SKILL.md file, put it in the right directory, and Claude follows them.
Start with one skill that automates your most repeated workflow. Get the description right so it triggers reliably. Add counterexamples when the output drifts. Then build the next one.
The compound effect is real. A developer with ten well-tuned skills is not just faster — they are more consistent. Every commit message follows the same standard. Every PR description has the same structure. Every deploy follows the same safety checks. That consistency compounds over weeks and months.
At Tally Digital, we build AI-powered tools and automations for New Zealand businesses. If you want help setting up Claude Code workflows, building custom skills for your team, or integrating AI into your development process, get in touch.
Share this article