Custom Commands

Custom commands in Claude Code are reusable prompts with bash interpolation and file references. The repetitive things you ask the AI to do can become one-word invocations.

March 30, 20264 min read5 / 8

After working with Claude Code for a while, patterns emerge. You ask for the same things repeatedly — "write tests for this file," "review this for security issues," "generate a migration for this schema change." Each time, you type out the full context, the requirements, the constraints.

Custom commands let you capture those patterns once and invoke them in one word.

How Commands Work

A custom command is a markdown file in .claude/commands/. When you type /command-name in Claude Code, the file's contents become your prompt.

Plain text
project/ └── .claude/ └── commands/ ├── test.md ├── review.md └── migrate.md
Plain text
> /test @src/components/UserCard.tsx > /review @src/api/payments.ts > /migrate add subscription tier to User model

Clean, reusable, consistent.

Creating a Command

Create a markdown file in .claude/commands/:

MARKDOWN
<!-- .claude/commands/test.md --> Write comprehensive Vitest unit tests for: $ARGUMENTS Requirements: - Use React Testing Library for component tests - Test happy paths, edge cases, and error states - Mock external dependencies at the module level with vi.mock() - Use descriptive test names: "should [behavior] when [condition]" - Group related tests with describe() blocks - Use userEvent for user interactions, not fireEvent After writing the tests, run them. If any fail, fix the failures before finishing.

Now /test @src/components/Button.tsx expands to that full prompt, with $ARGUMENTS substituted with @src/components/Button.tsx.

$ARGUMENTS: Dynamic Interpolation

$ARGUMENTS captures everything after the command name.

Plain text
Command: "Analyze this for security issues: $ARGUMENTS" Invocation: /security @src/api/auth.ts @src/middleware/auth.ts Result: "Analyze this for security issues: @src/api/auth.ts @src/middleware/auth.ts"

The @file references get resolved — Claude Code reads the actual file contents.

You can use $ARGUMENTS multiple times or use it as part of a larger sentence:

MARKDOWN
Explain $ARGUMENTS to a developer who is new to this codebase. Include: - What it does at a high level - Why it exists (what problem it solves) - Any non-obvious decisions - What someone would need to know to modify it safely

File References in Commands

Commands can embed @ file references that load with the command every time:

MARKDOWN
<!-- .claude/commands/pr-review.md --> Review the changes for: $ARGUMENTS Check for: 1. Correctness — does it do what it's supposed to? 2. Security — any potential vulnerabilities? 3. Performance — anything that could be slow at scale? 4. Conventions — does it follow the patterns in @CLAUDE.md? 5. Test coverage — are the tests adequate? For each issue found, give: file, line number, what's wrong, what to do instead.

The @CLAUDE.md reference loads your project conventions automatically into every PR review. The AI always checks against your actual standards, not generic ones.

Bash Interpolation

Commands can include bash expressions that evaluate at invocation time:

MARKDOWN
<!-- .claude/commands/standup.md --> Summarize my recent git work for a standup update. Today: $(date +%Y-%m-%d) Recent commits: $(git log --since="48 hours ago" --oneline --author="$(git config user.name)") Format the summary as: - What I finished - What I'm working on - Any blockers mentioned in commit messages

Every invocation is fresh — the date and git log reflect the current state.

Other useful bash expressions:

MARKDOWN
Current branch: $(git branch --show-current) Uncommitted changes: $(git diff --name-only) Last test run: $(cat .last-test-output 2>/dev/null || echo "no recent test output") Package versions: $(cat package.json | jq '.dependencies | to_entries | map("\(.key): \(.value)") | .[:10] | .[]' 2>/dev/null)

Useful Command Ideas

Build these over time as you notice repetitive patterns:

Test generation:

MARKDOWN
<!-- .claude/commands/test.md --> Write Vitest tests for $ARGUMENTS. Follow the patterns in @src/__tests__/UserCard.test.tsx as a reference. Test: happy paths, edge cases, error handling, and loading states. Run after writing — fix failures before finishing.

Refactor:

MARKDOWN
<!-- .claude/commands/refactor.md --> Refactor $ARGUMENTS for readability and maintainability. Constraints: - Max function complexity: 5 - Max function length: 50 lines - No behavior changes — structure only - No new dependencies - Run tests after and fix any regressions

Conventional commit message:

MARKDOWN
<!-- .claude/commands/commit.md --> Write a conventional commit message for the staged changes. Staged diff: $(git diff --staged) Format: <type>(<scope>): <short description> Types: feat, fix, refactor, test, docs, chore, perf First line: under 72 characters Body (if non-trivial): explain WHY, not what — the diff shows what.

Type generation:

MARKDOWN
<!-- .claude/commands/types.md --> Generate TypeScript types for $ARGUMENTS. Requirements: - interface for objects, type for unions/primitives - readonly where appropriate - JSDoc comments for non-obvious fields - No `any` types - Export everything

Security review:

MARKDOWN
<!-- .claude/commands/security.md --> Review $ARGUMENTS for security vulnerabilities. Check for: - SQL injection / NoSQL injection - Missing input validation - Missing authorization checks - Exposed secrets or credentials - XSS vulnerabilities - Unsafe use of eval() or similar - Missing rate limiting on sensitive endpoints Report: file, line, vulnerability type, severity (high/medium/low), remediation. Do not fix anything — report only.

Team Commands vs. Personal Commands

Team commands live in .claude/commands/ and get committed to the repo. Everyone on the team can use them. Good for shared workflows: the test format your team agreed on, the PR review checklist, the commit message convention.

Personal commands live in ~/.claude/commands/ (home directory). Available across all your projects. Good for personal preferences and tools you're experimenting with.

Building Your Toolkit

The shift commands enable: instead of thinking "how do I get the AI to do X," you think "what do I ask repeatedly that's worth capturing?"

Start small. One command a week:

  • Week 1: test generation with your project's test patterns
  • Week 2: commit message that follows your team's convention
  • Week 3: the code review checklist from your team's PR template
  • Week 4: refactor with your complexity and size constraints

By the end of a month, you have a personal toolkit that makes every session faster — and consistent in ways that matter to your team.

Enjoyed this? Get more like it.

Deep dives on system design, React, web development, and personal finance — straight to your inbox. Free, always.