Writing Claude Code skills -- how to build your own step-by-step skill files
A Claude Code skill is a markdown file that Claude reads and executes as a precise, repeatable instruction set. Skills are what make the difference between asking Claude to "add a feature" and getting inconsistent results, versus running /feature-module task and getting 7 correctly structured files every time.
This post covers how to write skill files that Claude actually follows -- the structure, the step patterns, and the small details that prevent drift.
What a skill is
A skill is a .md file in .claude/skills/ (or .claude/commands/). When you type /skill-name in Claude Code, Claude reads the file and executes its instructions.
The difference between a skill and a regular prompt:
- A prompt is typed once and forgotten. A skill is stored and reusable.
- A skill can be shared with the whole team via the repo. Everyone gets the same behavior.
- Claude reads the skill fresh each time, so the instructions are always in context.
- Skills can include conditionals, file reads, API calls, and multi-step sequences.
If you find yourself repeating the same multi-step prompt more than twice, write a skill.
File location and naming
.claude/
skills/
feature-module/
SKILL.md <-- full skill instructions
nextjs-api-route/
SKILL.md
commands/
generate-post.md <-- simpler single-file format
Both locations work. Use skills/<name>/SKILL.md for complex skills with supporting files. Use commands/<name>.md for shorter, self-contained skills.
The filename (without extension) becomes the slash command. feature-module/SKILL.md is invoked with /feature-module.
Anatomy of a skill file
Skills in .claude/skills/<name>/SKILL.md use YAML frontmatter followed by markdown content:
Frontmatter (required):
---
name: feature-module
description: One sentence describing what this skill does and when Claude should trigger it automatically.
---Title -- a short H1 after the frontmatter:
# Feature Module Skill
Steps -- the core of the skill, numbered and explicit:
- Read an existing module in
modules/to understand the structure. - Create
modules/<name>/<name>.schema.tswith a pgTable definition. - ...
Usage -- shows Claude the expected argument format:
/feature-module <name>
Example:
/feature-module categories
The simpler .claude/commands/<name>.md format skips frontmatter entirely. The first line is the description, followed directly by a steps section. Use it for short, self-contained skills.
Use the skills/ format when you want Claude to auto-trigger the skill from context -- the description frontmatter field is what Claude matches against.
Writing steps Claude will follow
The quality of a skill comes down to how you write its steps. These patterns work:
Be explicit about file paths. Vague instructions drift.
Bad:
Create the schema file for the module.
Good:
Create modules/<name>/<name>.schema.ts with a pgTable definition.
Table name: plural snake_case. Export name: camelCase + Table suffix (e.g. taskTable).
Specify what to read before writing. If the skill depends on existing conventions, tell Claude to read them.
- Read
db/drizzle.tsto understand how schemas and relations are registered. - Read an existing module (e.g.
modules/post/) to understand the 7-file structure. - Create the new module following the same pattern.
List every file to create. Do not say "scaffold the module" -- say exactly which files.
Create these 7 files:
modules/<name>/<name>.schema.tsmodules/<name>/<name>.relations.tsmodules/<name>/<name>.types.tsmodules/<name>/<name>.validation.tsmodules/<name>/<name>.repo.tsmodules/<name>/<name>.service.tsmodules/<name>/index.ts
Include the side effects. If creating a file requires updating another file, say so explicitly.
After creating the schema, add an export to db/schema.ts:
export * from '../modules/<name>/<name>.schema';After creating the relations file, import and spread it into the db client in db/drizzle.ts.
Use <name> as a placeholder. Claude replaces it with the argument from the slash command. This is the standard convention across all boilerplate skills.
Conditional logic
Skills can branch based on what Claude finds:
- Check if
modules/<name>/already exists.- If yes: stop and tell the user the module already exists.
- If no: proceed with creation.
- Check if the user passed a second argument (e.g.
/nextjs-api-route GET /api/tasks).- If a method and path were provided: generate a route handler for that method.
- If only a path was provided: generate GET and POST handlers.
Claude handles this branching in natural language -- no if/else syntax needed.
Reading files within a skill
Skills can instruct Claude to read files before acting:
- Read
CLAUDE.mdand find the "Blog Style Guide" section. Use the voice, keywords, and stop words defined there when generating the post. - Read
.envand extractAI_API_KEY(do not hardcode the value). - Fetch
http://localhost:3000/api/poststo get existing post slugs. Do not create a post whose title overlaps with an existing one.
This is how the generate-post skill works: it reads style conventions, checks for overlap, then creates the post -- all in one invocation.
Making skills ASCII-safe
If a skill generates content that will be stored in a database or sent over an API, add an explicit ASCII constraint:
- Use only ASCII characters in all generated text.
- Replace em dashes with
--, smart quotes with straight quotes ("/'). - Replace ellipsis characters with
.... - Never output Unicode beyond U+007F.
Without this, Claude occasionally outputs Unicode punctuation that breaks JSON serialization or causes display issues in terminals.
Usage section format
Always include a Usage section at the bottom of the skill. It shows Claude the expected argument format:
Single argument:
/feature-module <name>
Example:
/feature-module categories
Multiple argument patterns:
/nextjs-api-route <METHOD> <path>
/nextjs-api-route <path> (generates GET + POST)
Examples:
/nextjs-api-route POST /api/tasks
/nextjs-api-route /api/tasks/:id
A minimal working skill
Here is a complete skill that scaffolds a new React component. Create it at .claude/skills/new-component/SKILL.md:
Frontmatter:
---
name: new-component
description: Scaffold a new React component following the project's UI conventions.
---Title + steps:
Title: # New Component Skill
Steps:
- Read an existing component in
components/to understand the structure. - Create
components/<name>/<name>.tsx:- Named export (not default)
- Props typed with an interface:
interface <Name>Props { ... } - Use
cn()fromlib/utilsfor classname merging - Use CSS variables for colors, never hardcoded hex values
- Add
"use client"only if the component needs event handlers or hooks
- If the component has subcomponents, co-locate them:
components/<name>/<SubName>.tsx - Do not modify files in
components/ui/-- those are shadcn components.
Usage section:
## Usage
/new-component <name>
Example:
/new-component PricingCard
Connecting skills to CLAUDE.md
Skills and CLAUDE.md complement each other. CLAUDE.md defines your conventions globally -- naming rules, architecture patterns, anti-patterns. Skills reference those conventions rather than repeating them.
For example, a skill step can say: "Follow the naming conventions defined in CLAUDE.md" -- then list the specific rules:
- Table names are plural (
tasks,users) - Column names are
snake_case - TypeScript exports are
camelCase+Tablesuffix
This keeps skills short and ensures they stay in sync with your conventions automatically. If you update a convention in CLAUDE.md, every skill that references it picks up the change.
For a full guide on writing CLAUDE.md, see "How to configure CLAUDE.md for a Next.js project."
Takeaway
A good skill is explicit about paths, files, and side effects. It tells Claude what to read before writing. It uses <name> as a placeholder and includes a Usage section with examples. Write one any time you repeat a multi-step prompt more than twice -- the investment pays back immediately.