Guides

How to Write a Claude Code Skill

Step-by-step guide to writing a SKILL.md file for Claude Code. Learn how to plan, structure, and test auto-activating skills with proper frontmatter, allowed-tools, dynamic context injection, and supporting files.

What Is a Skill?

A skill is an auto-activating instruction file that Claude Code loads when its trigger conditions are met. Unlike slash commands (which the user must explicitly invoke) or agents (which run as autonomous sub-agents), skills activate automatically based on context. They live at skills/[skill-name]/SKILL.md inside a plugin directory and contain YAML frontmatter followed by Markdown instructions.

Skills are the most common building block in the Tons of Skills ecosystem. Of the 2,834 skills available on the marketplace, the vast majority are SKILL.md files that enhance Claude Code’s behavior for specific tasks: code review, deployment, API integration, testing, and more.

This guide walks you through building a skill from scratch, covering every decision you need to make along the way.

Step 1: Plan Your Skill

Before writing any Markdown, answer these questions:

What problem does this skill solve? A good skill addresses a repeatable task that benefits from structured instructions. “Help me write unit tests for React components” is a good scope. “Do everything related to my project” is too broad.

When should this skill activate? Claude Code reads the description field to decide when to load your skill. Think about the exact phrases a developer would say or the context that should trigger activation.

What tools does Claude need? Every tool you grant is a permission. Grant only what the skill actually requires. A skill that reads configuration files needs Read and Glob but probably not Write or Bash.

What is the expected output? Define what success looks like. A code review skill should produce structured feedback. A deployment skill should produce a checklist with pass/fail status.

Planning Checklist

Write down:

  1. A one-sentence purpose statement
  2. Three to five trigger phrases users might say
  3. The minimum set of tools required
  4. The expected output format
  5. Any prerequisites (runtime, language, framework)

Step 2: Create the Directory Structure

Skills live inside a plugin at skills/[skill-name]/SKILL.md. The skill name must be lowercase kebab-case.

my-plugin/
├── .claude-plugin/
│   └── plugin.json
├── README.md
└── skills/
    └── react-test-writer/
        ├── SKILL.md
        ├── patterns.md          # Optional supporting file
        └── examples/
            └── component.test.tsx  # Optional example file

Create the directory:

mkdir -p skills/react-test-writer
touch skills/react-test-writer/SKILL.md

Step 3: Write the Frontmatter

The YAML frontmatter block at the top of SKILL.md configures how Claude Code discovers and loads your skill. Here is the complete set of fields:

---
name: react-test-writer
description: |
  Write comprehensive unit tests for React components using Testing Library
  and Vitest. Trigger phrases: "write tests for this component", "add unit
  tests", "test this React component", "generate component tests".
allowed-tools: Read, Write, Edit, Bash(npx vitest:*), Glob, Grep
version: 1.0.0
author: Your Name <you@example.com>
license: MIT
compatible-with: claude-code
tags: [testing, react, vitest, unit-tests]
---

Required Fields

FieldPurpose
nameUnique identifier in kebab-case. Must match the directory name.
descriptionTells Claude Code when to activate this skill. Include trigger phrases. This is the single most important field for discoverability.
allowed-toolsComma-separated list of tools the skill may use.
versionSemantic version string.
authorName and optional email.
licenseSPDX license identifier.

Optional Fields

FieldPurposeExample
modelOverride the LLM model.sonnet, haiku, opus
contextRun in a forked sub-agent.fork
agentSub-agent type when context is fork.Explore
user-invocableSet to false to hide from the slash menu.false
argument-hintAutocomplete hint shown in the slash menu."<component-path>"
hooksLifecycle hooks for pre/post tool calls.{ pre-tool-call: ... }
compatibilityRuntime requirements."Node.js >= 18"
compatible-withPlatform compatibility.claude-code, cursor
tagsDiscovery tags for search and categorization.[testing, react]

Writing a Strong Description

The description field is a paragraph, not a sentence. Claude Code uses it as the primary signal for auto-activation. Follow these rules:

  1. Start with a verb phrase: “Write”, “Analyze”, “Deploy”, “Generate”
  2. Describe the specific task clearly
  3. List three to five trigger phrases users might say
  4. Mention key technologies or frameworks

Good description:

description: |
  Write comprehensive unit tests for React components using React Testing
  Library and Vitest. Covers render tests, user interaction tests, async
  behavior, and snapshot tests. Trigger phrases: "write tests for this
  component", "add unit tests", "test this React component".

Bad description:

description: "Helps with testing"

Step 4: Choose Your Allowed Tools

The allowed-tools field implements the principle of least privilege. Only grant the tools your skill actually needs.

Available Tools

ToolPurposeWhen to Include
ReadRead files from diskAlmost always needed
WriteCreate new filesWhen generating new files
EditModify existing filesWhen updating existing files
BashExecute shell commandsWhen running CLI tools
GlobFind files by patternWhen searching for files by name
GrepSearch file contentsWhen searching inside files
WebFetchFetch URLsWhen pulling remote data
WebSearchSearch the webWhen looking up documentation
TaskCreate sub-tasksFor complex multi-step workflows
TodoWriteWrite to the todo listFor tracking work items
NotebookEditEdit Jupyter notebooksFor data science workflows
AskUserQuestionPrompt the userWhen user input is needed
SkillInvoke other skillsFor skill composition

Scoping Bash Commands

You can restrict Bash to specific commands using a prefix pattern:

# Allow only npm and npx commands
allowed-tools: Read, Bash(npm:*), Bash(npx:*), Glob

# Allow only git commands
allowed-tools: Read, Bash(git:*), Glob, Grep

# Allow only python3
allowed-tools: Read, Write, Bash(python3:*), Glob

This prevents the skill from running arbitrary shell commands while still enabling the specific CLI tools it needs.

Step 5: Write the Skill Body

The body of SKILL.md is Markdown that instructs Claude Code how to perform the task. Think of it as a detailed playbook that a skilled developer would follow.

# Skill Name

Brief one-sentence overview of what this skill does.

## Overview

A paragraph explaining the skill's purpose, approach, and scope.

## Prerequisites

- Runtime requirements
- Required packages or tools
- Expected project structure

## Instructions

Step-by-step instructions Claude Code will follow:

1. First, do this
2. Then do that
3. Finally, verify the result

## Output

Describe the expected output format. Use a template if the output
should be structured.

## Error Handling

- If X happens, do Y
- If Z is missing, report it and stop

## Examples

Show concrete examples of input and expected output.

Writing Effective Instructions

Be specific, not vague. Instead of “write good tests”, say “write a test file that imports the component, renders it with default props, asserts the presence of key elements, simulates user interactions with userEvent, and verifies state changes.”

Use numbered steps. Claude Code follows numbered instructions more reliably than prose paragraphs.

Include code templates. Show the exact structure you want in the output:

## Output Format

Generate a test file with this structure:

\```typescript
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { describe, it, expect } from 'vitest';
import { ComponentName } from './ComponentName';

describe('ComponentName', () => {
  it('renders without crashing', () => {
    render(<ComponentName />);
    expect(screen.getByRole('...')).toBeInTheDocument();
  });

  it('handles user interaction', async () => {
    const user = userEvent.setup();
    render(<ComponentName />);
    await user.click(screen.getByRole('button'));
    expect(screen.getByText('...')).toBeVisible();
  });
});
\```

Specify error handling. Tell Claude what to do when things go wrong: missing files, failed tests, unsupported configurations.

Step 6: Add Supporting Files

Skills can reference additional files using relative Markdown links. Claude Code follows these links with the Read tool when it needs the content.

For API patterns, see [API Reference](reference.md).
For working examples, see [Examples](examples/sample.tsx).

Common Supporting Files

FilePurpose
reference.mdAPI documentation, configuration options
patterns.mdCode patterns and anti-patterns
checklist.mdValidation checklist
examples/*.tsWorking code examples

The ${CLAUDE_SKILL_DIR} variable resolves to the skill’s directory at runtime. Use it in bash commands within the skill body:

Read the configuration template:
\```bash
cat ${CLAUDE_SKILL_DIR}/templates/config.yaml
\```

Step 7: Dynamic Context Injection (DCI)

DCI lets you inject runtime data into the skill body before Claude processes it. This eliminates tool call round-trips for discovery data like git status, installed versions, or environment details.

Syntax

Place a shell command on its own line using backtick syntax:

## Current Environment

!`node --version 2>/dev/null || echo 'Node.js not installed'`

!`git branch --show-current 2>/dev/null || echo 'not a git repo'`

!`cat package.json | jq -r '.dependencies | keys[]' 2>/dev/null || echo 'no package.json'`

When the skill activates, Claude Code runs these commands and replaces them with their stdout output. The skill then sees the actual data instead of the commands.

DCI Best Practices

  1. Always add fallbacks. Use || echo 'fallback message' to handle missing tools or files.
  2. Keep injections small. Inject summaries, not full file contents. A list of dependency names is useful; the entire node_modules tree is not.
  3. Use 2>/dev/null to suppress stderr noise.
  4. Avoid side effects. DCI commands run at activation time. Never modify files or state in a DCI command.

Step 8: Test Your Skill

Manual Testing

  1. Install the plugin in a test project:

    claude /plugin add /path/to/your/plugin
  2. Say one of your trigger phrases and verify the skill activates.

  3. Check that the output matches your expected format.

  4. Test edge cases: missing files, wrong project type, empty input.

Validation

Run the universal validator against your skill:

# Standard validation (Anthropic minimum requirements)
python3 scripts/validate-skills-schema.py --verbose skills/react-test-writer/SKILL.md

# Enterprise validation (100-point rubric)
python3 scripts/validate-skills-schema.py --enterprise --verbose skills/react-test-writer/SKILL.md

The enterprise validator scores your skill on a 100-point rubric covering frontmatter completeness, body structure, code examples, error handling, and documentation quality. Aim for a score of 70 or higher (B grade) for marketplace submission.

Common Validation Errors

ErrorFix
Missing name fieldAdd name matching the directory name
Invalid allowed-toolsCheck spelling; use exact tool names from the valid list
Description too shortWrite at least 2-3 sentences with trigger phrases
No version fieldAdd version: 1.0.0
Body too shortAdd sections: Overview, Instructions, Output, Examples

Full Working Example

Here is a complete SKILL.md for a React test writer skill:

---
name: react-test-writer
description: |
  Write comprehensive unit tests for React components using React Testing
  Library and Vitest. Analyzes component props, state, effects, and user
  interactions to generate thorough test coverage. Trigger phrases: "write
  tests for this component", "add unit tests", "test this React component",
  "generate component tests".
allowed-tools: Read, Write, Bash(npx vitest:*), Bash(npx tsc:*), Glob, Grep
version: 1.0.0
author: Your Name <you@example.com>
license: MIT
compatibility: "Node.js >= 18, React >= 18"
compatible-with: claude-code
tags: [testing, react, vitest, unit-tests, testing-library]
---

# React Test Writer

Generate comprehensive unit tests for React components with full coverage
of rendering, interactions, and edge cases.

## Overview

This skill analyzes a React component's source code, identifies its props
interface, state management, side effects, and user interaction handlers,
then generates a complete test file using React Testing Library and Vitest.

## Prerequisites

- Project uses React 18+ with TypeScript
- Vitest and @testing-library/react installed
- @testing-library/user-event installed

## Current Project State

!`cat package.json | jq '{react: .dependencies.react, vitest: .devDependencies.vitest, testingLibrary: .devDependencies["@testing-library/react"]}' 2>/dev/null || echo 'Could not read package.json'`

## Instructions

1. Read the target component file using the Read tool
2. Identify the component's props interface (TypeScript types or PropTypes)
3. List all state variables (useState, useReducer)
4. List all side effects (useEffect, useLayoutEffect)
5. List all event handlers and user interaction points
6. Check for conditional rendering branches
7. Generate a test file following the output format below
8. Run the tests with `npx vitest run <test-file> --reporter=verbose`
9. If tests fail, read the error output and fix the test file
10. Report the final test results

## Output

Generate a test file at `[component-dir]/[ComponentName].test.tsx`:

- One `describe` block per component
- Render tests for default props and each significant prop combination
- Interaction tests using `userEvent` for every click, type, and submit handler
- Async tests for any data fetching or effects
- Edge case tests for empty states, error states, and boundary values

## Error Handling

- If the component file does not exist, report the error and stop
- If testing dependencies are missing, list the install commands needed
- If tests fail after generation, analyze the failure and fix (up to 3 attempts)
- If the component uses unsupported patterns, document them and skip those tests

## Examples

**Input:** "Write tests for src/components/SearchBar.tsx"

**Expected output:** A file `src/components/SearchBar.test.tsx` containing
tests for rendering, typing in the input, submitting the form, clearing
the input, and handling empty search terms.

Next Steps

Once your skill is working, you can package it into a full plugin with a plugin.json, README.md, and optional commands and agents. See How to Build a Claude Code Plugin for the complete plugin creation workflow, or Publish to the Marketplace to share your skill with the community.