Tutorials13 min read

How to Create Custom Claude Code Skills: Complete Tutorial (2026)

Learn how to build custom Claude Code Agent Skills step by step — SKILL.md structure, frontmatter fields, arguments, dynamic context, and real examples.

How to Create Custom Claude Code Skills: A Step-by-Step Tutorial

If you've ever pasted the same multi-step instructions into Claude Code more than twice — a deploy checklist, a code review rubric, a "how our API is structured" primer — you've already found your first candidate for a Claude Code Skill. Skills are the mechanism Claude Code uses to package reusable instructions, scripts, and reference material into something Claude can load automatically or you can trigger with a slash command.

Unlike copy-pasting into CLAUDE.md, a skill's content only loads into context when it's actually used. That means you can build a library of dozens of specialized skills — deployment runbooks, API conventions, research workflows — without permanently bloating every conversation with tokens you don't need most of the time.

This tutorial walks through building skills from scratch: the file structure, the frontmatter fields that control behavior, and three working examples you can copy today.

What a Claude Code Skill Actually Is

A skill is a directory containing a required SKILL.md file. That file has two parts:

  • YAML frontmatter between --- markers — metadata that tells Claude when and how to use the skill
  • Markdown content — the actual instructions Claude follows when the skill runs
  • The directory name becomes the slash command. A skill at .claude/skills/deploy/SKILL.md gives you /deploy. Claude can also invoke matching skills automatically based on your conversation, using the description field to decide relevance — no typing required.

    Skills follow the open Agent Skills standard, so skills you write for Claude Code are portable across other tools that adopt the same spec.

    Skills vs. CLAUDE.md vs. Custom Commands

    ApproachWhen it loadsBest for
    CLAUDE.mdEvery single turn, alwaysFacts about your codebase that are always relevant
    Custom command (.claude/commands/)Only when invokedSimple, fixed prompts (still works, but skills supersede it)
    Skill (.claude/skills/)Only when invoked or matchedProcedures, checklists, workflows — anything that's long but situational

    If a section of your CLAUDE.md has grown from "a fact about the repo" into "a multi-step procedure," that's your signal to move it into a skill instead.

    Where Skills Live

    Skill location determines who can use it:

    LocationPathScope
    Personal~/.claude/skills//SKILL.mdAll your projects
    Project.claude/skills//SKILL.mdThis repo only (commit it to share with your team)
    Plugin/skills//SKILL.mdWherever the plugin is installed
    EnterpriseManaged settingsEvery user in your org

    Project skills also load from nested .claude/skills/ directories in monorepos, so a package at apps/web/ can ship its own skills that apply only when you're working inside it.

    Step-by-Step: Build Your First Skill

    Let's build a skill that summarizes uncommitted git changes and flags anything risky — a task you'd otherwise re-explain to Claude every time.

    Step 1 — Create the directory:

    bashmkdir -p ~/.claude/skills/summarize-changes

    Step 2 — Write SKILL.md:

    yaml---
    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 — missing error handling, hardcoded values, or
    tests that need updating. If the diff is empty, say there are no
    uncommitted changes.

    The ` !git diff HEAD ` line is dynamic context injection: Claude Code runs that shell command before Claude ever sees the prompt, and swaps in the actual output. Claude reasons over your real, current diff — not a guess based on open files.

    Step 3 — Test it. Make a small edit in a git repo, start Claude Code, and either ask a natural question (What did I change?) or invoke it directly with /summarize-changes. Either path should return a summary grounded in your live diff.

    The Frontmatter Fields That Matter Most

    All frontmatter fields are optional except that description is strongly recommended — it's what Claude matches against your conversation to decide when to auto-load the skill. The fields worth knowing early on:

    • disable-model-invocation: true — only you can trigger the skill with /name. Use this for anything with side effects: deploys, commits, sending Slack messages. You don't want Claude deciding on its own that it's time to ship.
    • allowed-tools — grants specific tools without a permission prompt while the skill is active, e.g. allowed-tools: Bash(git add ) Bash(git commit ).
    • argument-hint and arguments — declare named parameters for $ARGUMENTS, $0/$1, or $name substitution in the skill body.
    • context: fork plus agent — runs the skill in an isolated subagent (built-in Explore, Plan, general-purpose, or a custom agent), rather than inline in your current conversation.
    • user-invocable: false — hides the skill from the / menu entirely; useful for background knowledge Claude should know but that isn't a meaningful action for a human to run.

    Example: A Skill With Arguments

    Skills that take arguments are ideal for repeatable, parameterized workflows — like fixing a specific ticket:

    yaml---
    name: fix-issue
    description: Fix a GitHub issue
    disable-model-invocation: true
    ---
    
    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 commit

    Running /fix-issue 123 replaces $ARGUMENTS with 123, and Claude receives the fully expanded instruction. For multiple positional values, use $0, $1, $2 (or declare named arguments in frontmatter) instead of one combined string:

    yaml---
    name: migrate-component
    description: Migrate a component from one framework to another
    ---
    
    Migrate the $0 component from $1 to $2.
    Preserve all existing behavior and tests.

    /migrate-component SearchBar React Vue maps cleanly to SearchBar, React, and Vue.

    Example: A Skill That Bundles a Script

    Skills aren't limited to prose — they can ship and execute real code. This keeps SKILL.md focused while offloading heavy lifting to a script:

    textmy-skill/
    ├── SKILL.md           # required — overview and navigation
    ├── reference.md        # detailed docs, loaded only when needed
    └── scripts/
        └── helper.py       # executed, not loaded into context

    Reference the script from SKILL.md using ${CLAUDE_SKILL_DIR}, which resolves correctly no matter where the skill is installed:

    yaml---
    name: codebase-visualizer
    description: Generate an interactive tree visualization of your codebase. Use when exploring a new repo or identifying large files.
    allowed-tools: Bash(python3 *)
    ---
    
    Run the visualization script from your project root:
    
        python3 ${CLAUDE_SKILL_DIR}/scripts/visualize.py .
    
    This creates codebase-map.html and opens it in your default browser.

    This pattern generalizes well: dependency graphs, coverage reports, schema diagrams — anywhere a deterministic script beats free-form generation.

    Injecting Dynamic Context the Right Way

    The ` ! ` syntax you saw in the git-diff example is worth understanding properly, because it's the difference between a skill that reasons about stale assumptions and one that reasons about ground truth. Every backtick-wrapped command in a skill body runs before Claude sees any of the content — the output is spliced in as plain text, and Claude never executes the command itself. That matters for two reasons:

  • It's preprocessing, not a tool call. Claude can't decide to skip it, retry it, or run it with different flags. If you need conditional logic, put the branching inside a script instead.
  • It only expands once. Command output is inserted as literal text and is not re-scanned for further ` !command ` placeholders, so a command can't chain into a second expansion.
  • For multi-line shell work, use a fenced block instead of the inline form:

    `

    markdown## Environment
    !

    node --version

    npm --version

    git status --short

    `

    This is especially useful for skills that need an up-to-date snapshot of environment state — dependency versions, running processes, current branch — without asking Claude to go fetch it via separate tool calls first.

    Running a Skill in an Isolated Subagent

    By default, a skill's instructions run inline, in your current conversation, with full access to everything you've discussed so far. Sometimes that's the wrong shape — a research task or a large refactor plan can pollute your main context with exploration noise you don't need to keep around. Add context: fork to run the skill in a forked subagent instead:

    yaml---
    name: deep-research
    description: Research a topic thoroughly
    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

    When this runs, a new isolated context spins up, the skill body becomes that subagent's entire task (it has no access to your prior conversation), and only the final summary comes back to your main session. The agent field picks the execution environment — built-in options are Explore, Plan, and general-purpose, or you can point it at any custom subagent defined in .claude/agents/.

    One caveat: context: fork only makes sense when your skill contains an actual task. If the body is just reference guidance ("use these API conventions"), a forked subagent receives guidance with nothing to act on and returns nothing useful.

    Controlling Who Can Trigger a Skill

    Two frontmatter fields govern invocation, and mixing them up is a common source of confusion:

    FrontmatterYou can run itClaude can run it automatically
    (default — neither set)YesYes
    disable-model-invocation: trueYesNo
    user-invocable: falseNoYes

    Use disable-model-invocation: true for anything you want to gate behind explicit human action — /deploy, /commit, /send-slack-message. Use user-invocable: false for the opposite case: background knowledge Claude should apply automatically (say, a legacy-system-context skill explaining a quirky old subsystem) where typing /legacy-system-context yourself wouldn't be a meaningful action.

    You can also lock down skill access globally through permission rules rather than editing individual files:

    text# Allow only specific skills
    Skill(commit)
    Skill(review-pr *)
    
    # Deny specific skills
    Skill(deploy *)

    This is worth doing on any shared or CI-facing Claude Code setup, since a project skill checked into .claude/skills/ can grant itself tool access via allowed-tools — review skills the same way you'd review a dependency before trusting a new repo.

    Evaluating Whether a Skill Actually Works

    Seeing Claude trigger your skill tells you it found it — not that the output is any good. The reliable way to check both is a baseline comparison: run the same realistic prompts in a fresh session with the skill available, then again with it disabled, and compare results. Do this in a fresh session each time; leftover context from writing the skill will hide gaps in your instructions that a new user would immediately hit.

    Anthropic ships a skill-creator plugin that automates this loop:

    text/plugin install skill-creator@claude-plugins-official

    After installing and running /reload-plugins, ask Claude to evaluate my summarize-changes skill with skill-creator. It walks you through recording test cases, runs each one in an isolated subagent, grades the output against your assertions, and benchmarks pass rate, token cost, and duration with the skill on versus off — so you can see whether a skill is worth its context overhead before you commit to it.

    Common Mistakes to Avoid

    • Vague descriptions. If Claude isn't triggering your skill automatically, the description probably doesn't contain the words a user would naturally say. Rewrite it with the actual trigger phrases in mind.
    • Skills that never end. A skill's rendered content stays in context for the rest of the session once invoked — it isn't re-read each turn. Keep the body under roughly 500 lines and push detail into linked reference files instead.
    • No manual-only gate on destructive actions. Anything that deploys, deletes, or sends external messages should set disable-model-invocation: true so Claude can't fire it unprompted.
    • Forgetting to test with a fresh session. If you author a skill in the same session you're using it, leftover context masks gaps in your instructions. Test in a clean session, and ideally compare behavior with the skill temporarily disabled via skillOverrides.

    Troubleshooting: Skill Not Triggering

    If Claude ignores a skill you expect it to use, work through these in order:

  • Check the wording of description. It needs to contain the phrases a user would naturally type, not internal jargon. "Use when the user asks what changed" beats "Handles diff summarization."
  • Confirm it's actually loaded. Ask Claude directly: What skills are available?
  • Try invoking it explicitly with /skill-name to isolate whether the problem is discovery (Claude doesn't know to use it) or execution (the instructions themselves don't work).
  • Check for malformed YAML. A broken frontmatter block still lets /skill-name work, but Claude has no description to match against, so automatic triggering silently fails. Run Claude Code with --debug to surface the parse error.
  • If a skill fires too often instead, tighten the description to be more specific, or add disable-model-invocation: true to remove it from automatic consideration entirely.

    If you're maintaining a large skill library, run /doctor periodically. As the number of skills grows, Claude Code trims less-used descriptions to stay within a context budget — /doctor shows which skills are being shortened or dropped, which is often the real reason a skill that "used to work" stops triggering.

    Sharing Skills With Your Team

    Skills are just files, so distribution follows normal version control:

    • Project skills — commit .claude/skills/ to your repo. Anyone who clones it and starts Claude Code inherits the skills automatically, no extra install step.
    • Plugins — package a skills/ directory inside a Claude Code plugin when you want to bundle skills together with agents, hooks, or MCP servers, or distribute them outside a single repo. Plugin skills are namespaced as plugin-name:skill-name, so they never collide with your personal or project skills.
    • Managed/enterprise — organizations can push skills to every developer through managed settings, which is the right layer for security or compliance procedures that shouldn't depend on an individual repo.

    For a monorepo, nested .claude/skills/ directories let individual packages ship skills that only apply while you're working inside them — a deploy skill at the repo root and another inside apps/web/.claude/skills/ can coexist, with Claude resolving to apps/web:deploy when there's a naming collision.

    Key Takeaways

    • A skill is a directory with SKILL.md: YAML frontmatter for behavior, markdown for instructions.
    • Personal skills (~/.claude/skills/) apply everywhere; project skills (.claude/skills/) are scoped and shareable via git.
    • description drives automatic triggering — write it the way a user would actually phrase the request.
    • Use disable-model-invocation: true for anything with side effects, and context: fork when a task should run isolated in a subagent.
    • Skills can bundle real scripts, not just prompts, referenced via ${CLAUDE_SKILL_DIR}.

    Next Steps

    Once you're comfortable authoring skills, pair them with Claude Code subagents for workflows that need isolated context, or read our CLAUDE.md best practices guide to decide what belongs in always-loaded memory versus an on-demand skill. If you're studying for the Claude Certified Architect exam, skill authoring patterns like these show up directly in the CCA curriculum — check out our CCA exam guide and practice test bank to get exam-ready.

    Ready to Start Practicing?

    300+ scenario-based practice questions covering all 5 CCA domains. Detailed explanations for every answer.

    Free CCA Study Kit

    Get domain cheat sheets, anti-pattern flashcards, and weekly exam tips. No spam, unsubscribe anytime.