Part 4 of 10
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."

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 problemsFormat as: Critical (must fix) → Warnings → SuggestionsEOF
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.mdExplain 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.mdFix GitHub issue #$1.Priority: $2Additional context: $31. Read the issue description2. Find the relevant code3. Implement the fix4. Write tests5. 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 issuesargument-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 commitallowed-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: $ARGUMENTSOtherwise, 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 analysismodel: 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 explanationmodel: 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 commithooks:PostToolUse:- matcher: "Edit|Write"hooks:- type: commandcommand: "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
Recommended Split
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.

1. /review — Code Review
---description: Comprehensive code reviewargument-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 commitallowed-tools: Bash(git *)---1. Run `git diff --staged` to see changes2. Analyze what was changed and why3. Create a commit message:- Format: type(scope): description- Types: feat, fix, refactor, docs, test, chore- Under 72 chars- Body explains WHY if non-obvious4. Execute the commit
3. /test:generate — Test Creation
---description: Generate comprehensive testsargument-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 systematicallyargument-hint: [description of the problem]---Debug: $ARGUMENTS## Process1. 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 flow5. Fix: Implement and verify the solution## Output- Root cause- Affected code paths- Fix with explanation- Prevention strategy
5. /security — Security Audit
---description: Security vulnerability scanargument-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## OutputSeverity-ranked findings with:- Location (file:line)- Risk explanation- Remediation steps
6. /refactor — Code Improvement
---description: Refactor while maintaining behaviorargument-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 passExplain each change.
7. /explain — Code Explanation
---description: Explain code for someone newargument-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 gotchasAssume reader knows the language but not this codebase.
8. /optimize — Performance Analysis
---description: Analyze and optimize performanceargument-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 verificationallowed-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 readyRun checks and report: Ready 🚀 or Blocked 🛑 with issues.
10. /catchup — Context Restoration
---description: Rebuild context after /clearallowed-tools: Bash(git *)---I just cleared context. Help me catch up.1. Run `git diff main...HEAD --stat` to see what's changed2. Summarize the changes on this branch3. Read the key modified files4. 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 APIargument-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## Instructions1. Parse the arguments: $ARGUMENTS2. Use curl to POST to the API with template_id, text0, text13. Return the generated meme URL4. 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 Imagenargument-hint: "prompt" [output-path]allowed-tools: Bash(node *)---Generate an image using Google Imagen.## Instructions1. Parse the prompt from: $ARGUMENTS2. 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 nodeconst 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:
- Go to Google AI Studio
- Create a new API key
- Either set
GOOGLE_AI_API_KEYenv 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 entirelyallow_adult— Adults only (default)allow_all— Adults and children
imageSize— Resolution:1Kor2K(Standard/Ultra models only)
Available models:
imagen-4.0-generate-001— Standard, best qualityimagen-4.0-ultra-generate-001— Ultra, highest qualityimagen-4.0-fast-generate-001— Fast, lower latencyimagen-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:
- Check the path — Is it in
.claude/commands/or~/.claude/commands/? - Check the extension — Must be
.md - Run
/help— Your command should appear in the list - Check frontmatter — YAML must be valid (proper indentation, quotes around strings with special chars)
Quick Reference
# Create command directorymkdir -p .claude/commands# Basic commandecho "Do something" > .claude/commands/name.md# Command with namespacemkdir -p .claude/commands/gitecho "Commit stuff" > .claude/commands/git/commit.md# View available commands/help
Frontmatter fields:
---description: Shows in /helpargument-hint: [what-to-pass]allowed-tools: Bash(git *), Read, Writemodel: claude-3-5-haiku-20241022hooks:PostToolUse:- matcher: "Edit"hooks:- type: commandcommand: "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.
