Tutorials
13 min read

Claude Code Mastery Part 4: Custom Commands

Build your personal command library. Turn complex workflows into single keystrokes with custom slash commands, arguments, frontmatter, and hooks.

Jo Vinkenroye
January 15, 2026
Claude Code Mastery Part 4: Custom Commands

Here's something I noticed after a few weeks of using Claude Code: I kept typing the same prompts over and over. "Review this code for security issues." "Generate tests following our project patterns." "Create a commit with a good message."

Me typing 'review this code for security issues' for the 47th time - Again
Me typing 'review this code for security issues' for the 47th time - Again

Every repeated prompt is wasted keystrokes. Custom slash commands fix that.

What Are Custom Commands?

Custom commands are markdown files that become slash commands. Put a file called review.md in the right folder, and suddenly /review is a command you can run anytime.

.claude/commands/
├── review.md → /review
├── test.md → /test
└── git/
├── commit.md → /git:commit
└── pr.md → /git:pr

The magic: these commands can accept arguments, specify which tools Claude can use, define hooks that run automatically, and even force a specific model. They're not just saved prompts—they're programmable workflows.

Your First Command in 60 Seconds

Step 1: Create the Directory

mkdir -p .claude/commands

Step 2: Create a Command File

cat > .claude/commands/review.md << 'EOF'
Review the staged changes for:
- Security vulnerabilities
- Performance issues
- Missing error handling
- Type safety problems
Format as: Critical (must fix) → Warnings → Suggestions
EOF

Step 3: Use It

/review

That's it. Claude executes your instructions as if you'd typed them out.

Arguments: Making Commands Dynamic

Static commands are useful, but dynamic commands are powerful. The $ARGUMENTS placeholder captures everything after the command name.

Basic Arguments

# .claude/commands/explain.md
Explain how $ARGUMENTS works in this codebase.
Show me the key files, the data flow, and any gotchas.

Usage:

/explain the authentication system
/explain the payment processing flow
/explain how websockets connect to the backend

Whatever you type after /explain becomes $ARGUMENTS.

Positional Arguments

Need more control? Use $1, $2, $3 for specific positions:

# .claude/commands/fix-issue.md
Fix GitHub issue #$1.
Priority: $2
Additional context: $3
1. Read the issue description
2. Find the relevant code
3. Implement the fix
4. Write tests
5. Create a commit referencing the issue

Usage:

/fix-issue 1234 high "user reported login failures on mobile"

Here $1 = "1234", $2 = "high", $3 = "user reported login failures on mobile".

Frontmatter: Command Superpowers

The real power comes from YAML frontmatter at the top of your command file. This lets you configure permissions, hints, models, and more.

The Basics

---
description: Review code for security issues
argument-hint: [file-or-directory]
---
Review $ARGUMENTS for security vulnerabilities including:
- SQL injection
- XSS
- Authentication bypasses

The description shows up when you run /help. The argument-hint tells users what to pass.

Pre-Approving Tool Permissions

Tired of approving the same git commands every time? Use allowed-tools:

---
description: Smart git commit
allowed-tools: Bash(git add:*), Bash(git status:*), Bash(git commit:*), Bash(git diff:*)
argument-hint: [optional message]
---
Analyze the staged changes and create a commit.
If a message is provided, use it: $ARGUMENTS
Otherwise, generate a descriptive message following conventional commits.
Requirements:
- Use format: type(scope): description
- Keep summary under 72 characters
- Body explains WHY, not WHAT

Now /git:commit runs without permission prompts for those specific git commands.

Forcing a Specific Model

Some commands work better with specific models:

---
description: Deep architecture analysis
model: claude-sonnet-4-5-20250929
---
Analyze the architecture of $ARGUMENTS.
Consider:
- Design patterns in use
- Coupling between modules
- Scalability implications
- Technical debt indicators

Use Haiku for quick tasks, Sonnet for balanced work, Opus for complex reasoning:

---
description: Quick code explanation
model: claude-3-5-haiku-20241022
---
Briefly explain what $ARGUMENTS does in 2-3 sentences.

Hooks in Commands

Commands can define their own hooks that run during execution:

---
description: Format and commit
hooks:
PostToolUse:
- matcher: "Edit|Write"
hooks:
- type: command
command: "npx prettier --write $FILE_PATH"
---
Make the requested changes to $ARGUMENTS.
After each edit, files will be auto-formatted.

This runs Prettier after every file edit—but only while this command is active.

Namespacing with Directories

Organize related commands in subdirectories. The folder name becomes a namespace:

.claude/commands/
├── git/
│ ├── commit.md → /git:commit
│ ├── pr.md → /git:pr
│ ├── branch.md → /git:branch
│ └── squash.md → /git:squash
├── test/
│ ├── unit.md → /test:unit
│ ├── e2e.md → /test:e2e
│ └── coverage.md → /test:coverage
└── docs/
├── readme.md → /docs:readme
└── api.md → /docs:api

This keeps your /help output organized and commands easy to discover.

Global vs Project Commands

Project Commands (Team)

your-project/.claude/commands/
  • Available only in this project
  • Commit to git—everyone on the team gets them
  • Great for project-specific workflows

Personal Commands (You)

~/.claude/commands/
  • Available in every project
  • Personal productivity boosters
  • Not shared with team

Keep global (personal):

  • /explain — Universal code explanation
  • /debug — Systematic debugging process
  • /review — General code review checklist

Keep project-specific (team):

  • /deploy — Your deployment steps
  • /test:e2e — Your test setup
  • /git:commit — Your team's commit conventions
  • /onboard — Project-specific context for new devs

10 Commands Worth Stealing

Here are battle-tested commands. Copy them, modify them, make them yours.

The Ten Commandments of Claude Code
The Ten Commandments of Claude Code

1. /review — Code Review

---
description: Comprehensive code review
argument-hint: [file or leave empty for staged changes]
---
Review $ARGUMENTS (or staged changes if not specified).
## Check For
- Security: OWASP Top 10, auth issues, data exposure
- Performance: N+1 queries, memory leaks, blocking calls
- Correctness: Edge cases, error handling, type safety
- Maintainability: Complexity, naming, duplication
## Output Format
### 🚨 Critical (blocks merge)
### ⚠️ Warnings (should fix)
### 💡 Suggestions (nice to have)

2. /git:commit — Smart Commits

---
description: Analyze changes and create commit
allowed-tools: Bash(git *)
---
1. Run `git diff --staged` to see changes
2. Analyze what was changed and why
3. Create a commit message:
- Format: type(scope): description
- Types: feat, fix, refactor, docs, test, chore
- Under 72 chars
- Body explains WHY if non-obvious
4. Execute the commit

3. /test:generate — Test Creation

---
description: Generate comprehensive tests
argument-hint: [file-to-test]
---
Generate tests for $ARGUMENTS.
## Include
- Happy path (expected usage)
- Edge cases (empty, null, boundaries)
- Error scenarios (invalid input, failures)
- Integration points (mocks for external deps)
## Requirements
- Match existing test patterns in this project
- Use the testing framework already in use
- Clear test names: "should [expected] when [condition]"

4. /debug — Systematic Debugging

---
description: Debug an issue systematically
argument-hint: [description of the problem]
---
Debug: $ARGUMENTS
## Process
1. Clarify: What's expected vs actual behavior?
2. Reproduce: What triggers the issue?
3. Isolate: Which component/function is responsible?
4. Trace: Follow the data flow
5. Fix: Implement and verify the solution
## Output
- Root cause
- Affected code paths
- Fix with explanation
- Prevention strategy

5. /security — Security Audit

---
description: Security vulnerability scan
argument-hint: [file, directory, or leave empty for full scan]
allowed-tools: Read, Grep, Glob
---
Audit $ARGUMENTS for security vulnerabilities.
## Check For
- Injection: SQL, command, XSS, template
- Auth: Weak passwords, session issues, CSRF
- Data: Exposure, logging secrets, insecure storage
- Config: Debug mode, default creds, missing headers
- Dependencies: Known CVEs
## Output
Severity-ranked findings with:
- Location (file:line)
- Risk explanation
- Remediation steps

6. /refactor — Code Improvement

---
description: Refactor while maintaining behavior
argument-hint: [file-or-function]
---
Refactor $ARGUMENTS.
## Goals
- Reduce complexity
- Improve readability
- Apply DRY
- Better naming
- Smaller functions (single responsibility)
## Constraints
- NO behavior changes
- Keep public API intact
- Existing tests must pass
Explain each change.

7. /explain — Code Explanation

---
description: Explain code for someone new
argument-hint: [file, function, or concept]
model: claude-3-5-haiku-20241022
---
Explain $ARGUMENTS.
## Cover
- What it does (purpose)
- How it works (step by step)
- Why it's designed this way
- Dependencies and side effects
- Potential gotchas
Assume reader knows the language but not this codebase.

8. /optimize — Performance Analysis

---
description: Analyze and optimize performance
argument-hint: [file-or-function]
---
Analyze $ARGUMENTS for performance.
## Examine
- Time complexity (Big O)
- Space complexity
- I/O operations
- Database queries (N+1?)
- Unnecessary allocations
## Output
- Current bottlenecks
- Specific optimizations
- Expected improvement
- Trade-offs involved

9. /ship — Pre-Deploy Checklist

---
description: Pre-deployment verification
allowed-tools: Bash(npm *), Bash(git *)
---
Pre-deploy checklist:
- [ ] Tests pass (`npm test`)
- [ ] Lint clean (`npm run lint`)
- [ ] Build succeeds (`npm run build`)
- [ ] No console.log/debugger statements
- [ ] Env vars documented
- [ ] No hardcoded secrets
- [ ] Error handling complete
- [ ] Migrations ready
Run checks and report: Ready 🚀 or Blocked 🛑 with issues.

10. /catchup — Context Restoration

---
description: Rebuild context after /clear
allowed-tools: Bash(git *)
---
I just cleared context. Help me catch up.
1. Run `git diff main...HEAD --stat` to see what's changed
2. Summarize the changes on this branch
3. Read the key modified files
4. Tell me where we likely are in this feature
$ARGUMENTS (if any additional context)

This pairs perfectly with the /clear + /catchup pattern from Part 2.

Beyond Code: Creative Commands

Commands aren't limited to code workflows. You can integrate any API or service Claude can call. Here are two I used while writing this very blog series:

/meme — Generate Memes

---
description: Generate a meme using Imgflip API
argument-hint: "top text" "bottom text" [template]
---
Generate a meme using the Imgflip API.
## API Details
- Endpoint: https://api.imgflip.com/caption_image
- Username: $IMGFLIP_USER
- Password: $IMGFLIP_PASS
## Popular Templates
- drake (181913649) - Drake Hotline Bling
- distracted (112126428) - Distracted Boyfriend
- changemymind (129242436) - Change My Mind
- twobuttons (87743020) - Two Buttons
- pikachu (155067746) - Surprised Pikachu
## Instructions
1. Parse the arguments: $ARGUMENTS
2. Use curl to POST to the API with template_id, text0, text1
3. Return the generated meme URL
4. Download to ./public/assets/blog/ if requested

I used this to generate the "distracted boyfriend" meme in Part 5 — developer distracted by "just ship it" while "write tests first" looks on.

/imagen — AI Image Generation

This is one of my favorites. Google's Imagen API lets you generate images directly from Claude Code. Here's a complete implementation:

---
description: Generate an image using Google Imagen
argument-hint: "prompt" [output-path]
allowed-tools: Bash(node *)
---
Generate an image using Google Imagen.
## Instructions
1. Parse the prompt from: $ARGUMENTS
2. Run: node ~/.claude/scripts/generate-image.js "prompt" "./output.png"
3. Report the saved file path

The real magic is in the script. Here's a complete implementation you can drop into ~/.claude/scripts/generate-image.js:

#!/usr/bin/env node
const https = require('https');
const fs = require('fs');
const path = require('path');
const API_KEY = process.env.GOOGLE_AI_API_KEY || 'your-api-key-here';
const MODEL = 'imagen-4.0-generate-001';
async function generateImage(prompt, outputPath) {
const requestBody = {
instances: [{ prompt }],
parameters: {
sampleCount: 1,
aspectRatio: '16:9',
personGeneration: 'dont_allow'
}
};
const body = JSON.stringify(requestBody);
const options = {
hostname: 'generativelanguage.googleapis.com',
port: 443,
path: `/v1beta/models/${MODEL}:predict?key=${API_KEY}`,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(body)
}
};
return new Promise((resolve, reject) => {
const req = https.request(options, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
const response = JSON.parse(data);
if (response.error) {
reject(new Error(response.error.message));
return;
}
if (response.predictions?.[0]) {
const imageData = response.predictions[0].bytesBase64Encoded;
const dir = path.dirname(outputPath);
if (dir && !fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(outputPath, Buffer.from(imageData, 'base64'));
resolve(outputPath);
}
});
});
req.on('error', reject);
req.write(body);
req.end();
});
}
const [prompt, outputPath = `./generated-${Date.now()}.png`] = process.argv.slice(2);
generateImage(prompt, outputPath).then(p => console.log(`Saved: ${p}`));

Getting an API Key:

  1. Go to Google AI Studio
  2. Create a new API key
  3. Either set GOOGLE_AI_API_KEY env var or paste directly in script

Usage examples:

/imagen "a minimalist logo for a coding blog, orange and white"
/imagen "abstract geometric pattern" ./public/hero.png
/imagen "futuristic terminal interface, dark theme, neon accents"

API Parameters:

  • sampleCount — Number of images to generate (1-4, default: 4)
  • aspectRatio — Output dimensions: 1:1, 3:4, 4:3, 9:16, 16:9 (default: 1:1)
  • personGeneration — Controls people in images:
    • dont_allow — Block people entirely
    • allow_adult — Adults only (default)
    • allow_all — Adults and children
  • imageSize — Resolution: 1K or 2K (Standard/Ultra models only)

Available models:

  • imagen-4.0-generate-001 — Standard, best quality
  • imagen-4.0-ultra-generate-001 — Ultra, highest quality
  • imagen-4.0-fast-generate-001 — Fast, lower latency
  • imagen-3.0-generate-001 — Previous generation

Some cover images and illustrations throughout this series? Generated with /imagen. The memes you see here? Created with /meme.

The point: if there's an API, you can wrap it in a command. Social media posting, translation, image generation, data lookups—anything becomes a slash command.

Command Design Principles

Single purpose — One command does one thing well. /review reviews, /test tests. Don't make Swiss Army knives.

Clear output format — Tell Claude exactly how to present results. Bullet points? Categories? Severity levels? Be explicit.

Include constraints — What should Claude NOT do? "No behavior changes" or "Don't modify tests" prevents overreach.

Use appropriate model — Haiku for quick lookups, Sonnet for most tasks, Opus for complex reasoning. Match the model to the job.

Pre-approve safe tools — If a command always needs git or npm, use allowed-tools to skip permission prompts.

Debugging Commands

If a command isn't working:

  1. Check the path — Is it in .claude/commands/ or ~/.claude/commands/?
  2. Check the extension — Must be .md
  3. Run /help — Your command should appear in the list
  4. Check frontmatter — YAML must be valid (proper indentation, quotes around strings with special chars)

Quick Reference

# Create command directory
mkdir -p .claude/commands
# Basic command
echo "Do something" > .claude/commands/name.md
# Command with namespace
mkdir -p .claude/commands/git
echo "Commit stuff" > .claude/commands/git/commit.md
# View available commands
/help

Frontmatter fields:

---
description: Shows in /help
argument-hint: [what-to-pass]
allowed-tools: Bash(git *), Read, Write
model: claude-3-5-haiku-20241022
hooks:
PostToolUse:
- matcher: "Edit"
hooks:
- type: command
command: "npm run lint"
---

Argument placeholders:

  • $ARGUMENTS — Everything after the command
  • $1, $2, $3 — Positional arguments

What's Next

Custom commands encode your workflows into reusable actions. But what if you want to share commands beyond your team? What if you want commands that work across different projects without copying files everywhere?

That's where Skills come in. In Part 5: Skills, we'll explore how to package commands for broader distribution and create more sophisticated automation.


Previous: Part 3: Project Configuration

Stay Updated

Get notified about new tutorials on Claude Code, productivity tips, and automation guides.

No spam, ever. Unsubscribe anytime.

Comments

Related Posts