AI Coding Tools & IDEs

Claude Code Slash Commands: Build Reusable Workflows

If you keep pasting the same prompt into Claude Code — “review this diff for security issues”, “write a conventional commit message”, “generate tests for this file” — you are re-typing work that belongs in a file. Claude Code slash commands are the fix. They turn repeated prompts into named, project-scoped workflows you can invoke in one keystroke.

This tutorial walks through the full surface area of Claude Code slash commands: the built-in ones worth knowing, how to build custom commands with arguments, how to wire in bash output and file references, and the decision points that separate a command from a hook or a subagent. Examples use Claude Code v2+ conventions.

If you are brand new to the CLI, start with our Claude Code setup and first workflow tutorial first — this post assumes you can already run claude in a project directory.

What Are Claude Code Slash Commands?

Claude Code slash commands are text files that Claude expands into a prompt when you type /command-name in an interactive session. They live in .claude/commands/ (project scope, shared via git) or ~/.claude/commands/ (personal scope, shared across your machine). Each file is a Markdown document with optional YAML frontmatter that configures the command’s description, allowed tools, and default model.

In practice, a slash command is a reusable prompt template. However, it is more than text substitution. Commands can accept positional arguments, execute shell snippets before the prompt reaches Claude, inline file contents by reference, and constrain which tools Claude is allowed to use for that invocation. That turns them into a lightweight scripting layer for your AI pair.

When to Use Slash Commands

Reach for a slash command when a prompt becomes a habit. If you find yourself typing variations of the same instruction across sessions — reviewing PRs, writing commit messages, scaffolding a new feature — it is worth 60 seconds to save it as a command. Future-you invokes it with /review instead of reconstructing the prompt from memory.

Slash commands also shine for team alignment. A project-scoped command checked into .claude/commands/review.md ensures every engineer on the team gets the same structured review, with the same criteria, in the same format. That consistency is hard to get from individual prompting habits.

Built-in Claude Code Slash Commands Worth Knowing

Before writing custom ones, it helps to know the built-ins. Typing /help inside Claude Code lists the full set, but these are the ones most developers use daily:

CommandWhat It Does
/clearClears conversation history and starts fresh
/compactSummarizes the current conversation to reclaim context
/initGenerates a CLAUDE.md file documenting the codebase
/reviewReviews the current branch against main (built-in)
/modelSwitches the active model (Opus, Sonnet, Haiku)
/permissionsOpens the permissions panel to allow or deny tools
/agentsLists and manages subagents
/hooksConfigures lifecycle hooks in settings
/costShows token usage and cost for the current session
/resumeResumes a previous session

The distinction that trips people up: /clear nukes history, /compact keeps the gist. Use /compact when you have made real progress and want Claude to remember the outcome without the full trace of tool calls that got you there.

How Custom Slash Commands Are Stored

A custom slash command is a Markdown file whose path determines the command name. Given a project root at ~/projects/myapp, the file ~/projects/myapp/.claude/commands/review.md registers /review (overriding the built-in for that project). A file at ~/projects/myapp/.claude/commands/db/migrate.md registers /db:migrate — subdirectories become namespaces separated by colons.

The directory layout looks like this:

.claude/
├── commands/
│   ├── review.md              → /review
│   ├── commit.md              → /commit
│   ├── test-gen.md            → /test-gen
│   └── db/
│       ├── migrate.md         → /db:migrate
│       └── seed.md            → /db:seed
├── agents/
└── settings.json

Project-scoped commands live in .claude/commands/ and should be checked into git so your team gets them too. Personal commands live in ~/.claude/commands/ and apply to every project on your machine. If both scopes define the same name, the project scope wins.

Your First Custom Slash Command

Here is a minimal working example. Create .claude/commands/explain.md in any project:

---
description: Explain what the selected code does in plain English
argument-hint: <file-or-symbol>
---

Read $ARGUMENTS and explain in 3–5 sentences what it does and why it exists.

Focus on the business purpose, not a line-by-line walkthrough.
If there is an obvious non-obvious gotcha, mention it.

Now, inside a Claude Code session, run /explain src/auth/session.ts — Claude receives the rendered prompt with $ARGUMENTS replaced by src/auth/session.ts, reads the file, and produces the explanation. The argument-hint field shows up in autocomplete so you remember what the command expects.

Frontmatter Fields You Will Actually Use

The YAML frontmatter at the top of a command file configures how it behaves. Most commands use a subset of these:

---
description: One-line summary shown in /help
argument-hint: <expected-args>
allowed-tools: Read, Grep, Bash(git diff:*), Bash(git log:*)
model: claude-haiku-4-5
disable-model-invocation: false
---

The description is the one-line summary in the command picker — keep it under 60 characters. The argument-hint is purely informational, displayed during autocomplete. The allowed-tools field is the interesting one: it narrows the tool set for this command to the ones listed. Restricting a read-only /explain command to Read, Grep prevents Claude from accidentally editing files while explaining them.

Notice the Bash(git diff:*) syntax. This allowlists any git diff invocation but blocks arbitrary shell commands. The :* means “any arguments after git diff“. Specific allowlists like this are the right default for commands that only need a narrow slice of the shell.

The model field pins the command to a specific model — useful when you want a fast command like /commit-message to always run on Haiku for speed, regardless of what the parent session is using.

Arguments: $ARGUMENTS, $1, $2, $3

Slash commands support two argument styles. $ARGUMENTS is the full argument string passed to the command. Positional parameters $1$2, and so on pick out individual tokens separated by whitespace.

For example, a command invoked as /feature auth add-2fa high-priority would see:

  • $ARGUMENTS = auth add-2fa high-priority
  • $1 = auth
  • $2 = add-2fa
  • $3 = high-priority

Use positional parameters when your command has a clear structure (“area, feature, priority”) and $ARGUMENTS when you want to accept freeform text like a commit scope or a bug description. Do not mix them if you can avoid it — pick one style per command for clarity.

Inlining Shell Output With the ! Prefix

Claude Code slash commands can execute shell commands before the prompt reaches the model, and inline the stdout into the prompt. Prefix a line with ! and it becomes a bash block that runs at expansion time.

Here is a realistic /commit command that writes a conventional commit message based on the actual staged diff:

---
description: Generate a Conventional Commits message from staged changes
allowed-tools: Bash(git diff:*), Bash(git log:*), Bash(git status:*)
model: claude-haiku-4-5
---

You are writing a commit message for the staged changes below.

Current status:
!`git status --short`

Staged diff:
!`git diff --staged`

Recent commits (for style reference):
!`git log -5 --oneline`

Write a Conventional Commits message following these rules:
- Type: feat, fix, refactor, chore, docs, test, perf, or build
- Scope: one-word area (e.g., auth, api, ui) if obvious, omit otherwise
- Subject: imperative mood, under 72 characters, no trailing period
- Body: only if the change is non-trivial, wrapped at 80 chars
- Output the message as a single code block, nothing else.

When you invoke /commit, Claude Code runs git status --shortgit diff --staged, and git log -5 --oneline locally, pastes the output into the prompt, and sends the whole thing to the model. You get a commit message grounded in the actual diff — not a hallucination about what you might have changed.

The allowed-tools field is critical here. Because the bash invocations are constrained to git diffgit log, and git status, the command cannot be abused into running arbitrary shell commands even if a prompt injection lands in a staged file.

Inlining File Contents With the @ Prefix

The @ prefix inlines file contents by path. It is similar to asking Claude to read a file with the Read tool, but happens at expansion time and does not consume a tool call.

Here is a /test-gen command that pulls in a target file plus the project’s testing conventions:

---
description: Generate unit tests for the given file using project conventions
argument-hint: <path-to-source-file>
allowed-tools: Read, Edit, Write, Bash(npm test:*)
---

Generate tests for the file below, following the conventions in @docs/testing.md.

Target file:
@$1

Existing test examples to match in style:
@tests/examples/basic.test.ts

Write tests that:
- Cover the happy path and at least two edge cases
- Use the test runner already configured in the project
- Place the new file next to the source as `<name>.test.ts`
- After writing, run the suite and fix any failing tests you introduced.

The @$1 pattern is powerful: it combines positional arguments with file inlining, so /test-gen src/utils/parser.ts reads src/utils/parser.ts into the prompt before any tool calls happen. For more depth on test generation with LLMs, see our guide on generating unit tests with large language models.

A Realistic /review Command

Here is a command that replaces the built-in /review with a stricter, project-specific version. Save as .claude/commands/review.md:

---
description: Review the current branch against main with project-specific checks
allowed-tools: Read, Grep, Bash(git diff:*), Bash(git log:*), Bash(git branch:*)
model: claude-opus-4-7
---

Review the changes on this branch against `main`.

Current branch:
!`git branch --show-current`

Changes vs main:
!`git diff main...HEAD --stat`

Full diff:
!`git diff main...HEAD`

Review against the project standards in @CONTRIBUTING.md and check specifically for:

1. **Security** — SQL injection, XSS, hardcoded secrets, unsafe deserialization
2. **Error handling** — unhandled promise rejections, swallowed exceptions, missing retries on external calls
3. **Test coverage** — every new branch of logic should have a test, flag gaps
4. **Backwards compatibility** — public API changes, database migrations, breaking schema edits
5. **Performance** — N+1 queries, unbounded loops, missing indexes on new query paths

Output format:
- Start with a verdict: APPROVE, APPROVE WITH CHANGES, or REQUEST CHANGES
- Group findings by severity (Blocker / Major / Minor / Nit)
- For each finding, cite the file and line (e.g., `src/auth.ts:42`)
- End with a one-line summary.

Do not make changes. This is review-only.

The command is pinned to Opus for depth, restricted to read-only tools, and grounded in the project’s contribution guide. Every engineer on the team gets the same review against the same rubric, without copy-pasting a prompt.

For the human side of reviewing changes, pair this with our guide on code review best practices.

Real-World Scenario: A Small Team Standardizing PR Workflow

Consider a typical mid-sized backend team — five to seven engineers working on an Express API with a shared Postgres database. Before slash commands, each engineer had their own habit of asking Claude for reviews, commit messages, and test scaffolds. The prompts drifted in quality, and reviews sometimes missed the same classes of bug the team had been burned by before.

Adopting project-scoped slash commands standardized the workflow. A /review command checked into .claude/commands/review.md enforced the team’s checklist: auth middleware changes, migrations, and rate-limit edits always got flagged as blockers. A /commit command produced uniformly formatted Conventional Commits messages. A /test-gen command referenced a shared @tests/examples/ directory so new tests matched the existing style.

The trade-off: the team had to maintain the commands as the codebase evolved. When the testing conventions changed, someone had to update the command file. However, that cost was offset by the consistency gain — a new hire’s first PR went through the same review as a senior engineer’s, because both had typed /review.

Slash Commands vs Hooks vs Subagents

The most common confusion is when to use each feature. The distinction comes down to who triggers the behavior:

FeatureTriggered ByBest For
Slash commandYou (manually, with /name)Repeated prompts you want to invoke on demand
HookClaude Code (automatically, on lifecycle events)Running linters, blocking commits, auto-formatting
SubagentClaude (delegated, via the Agent tool)Parallel work or protecting the main context window

A slash command is a named prompt you run. A hook is a shell command the harness runs at events like PostToolUse or Stop. A subagent is a nested Claude session that the main agent launches to offload work. If you want linting to happen every time Claude edits a file, that is a hook — see our guide on Claude Code hooks for automating linting, tests, and commits. If you want to trigger a structured review when you feel like it, that is a slash command.

When to Use Claude Code Slash Commands

  • You find yourself typing the same prompt more than twice
  • A prompt needs structured inputs (file paths, scopes, flags)
  • Your team needs consistent AI-assisted workflows across PRs
  • You want to restrict a workflow to a narrow tool allowlist
  • You want a command to always run on a specific model (Haiku for speed, Opus for depth)

When NOT to Use Claude Code Slash Commands

  • The workflow runs automatically on an event — use a hook instead
  • The task should happen in parallel or in an isolated context window — use a subagent instead
  • The prompt changes every time — a saved command adds friction, not value
  • The “command” is actually two or three unrelated asks glued together — split it up

Common Mistakes with Claude Code Slash Commands

  • Forgetting allowed-tools on commands that run bash. Without an allowlist, a command like /review can execute arbitrary shell commands if the prompt or diff contains injection content. Restrict to the minimum needed.
  • Using $ARGUMENTS when positional parameters would be clearer. If your command always takes three specific inputs, $1$2$3 are self-documenting. $ARGUMENTS invites ambiguity.
  • Committing personal commands to .claude/commands/. Commands in the project scope are seen by the whole team. Keep personal ergonomics in ~/.claude/commands/ instead.
  • Putting configuration that changes frequently in frontmatter. If you are constantly editing model: between Opus and Haiku, drop the field and let the parent session decide.
  • Making commands too long. A command that includes a 500-word prompt plus seven ! bash calls is hard to maintain. Break it into two commands or push the complexity into a subagent.
  • Not checking the file into git. Commands in .claude/commands/ only help the team if they are committed. Add the folder to your repo’s first PR, not a private stash.

Comparing Slash Commands Across AI Coding Tools

Claude Code is not the only agent with reusable prompt workflows — Cursor has project rules, Aider has chat modes, and Windsurf has its own Cascade workflows. However, Claude Code’s slash commands are the most file-first of the bunch: they are plain Markdown in a standard folder, with no vendor-specific editor required. For a broader look at the landscape, our AI code assistants comparison covers the trade-offs between the major tools.

Testing Your Slash Commands

A quick sanity check before committing a new command:

  1. Run /help and confirm the command appears with the expected description
  2. Invoke the command with an argument and watch the prompt render — use /compact first so you can see Claude’s full response without clutter
  3. Verify that allowed-tools blocks anything you did not intend (e.g., trigger a non-allowlisted bash command and confirm it is denied)
  4. Check that the command works in a fresh session, not just the one where you wrote it
  5. Ask a teammate to run it without explanation — if they cannot tell what it does from description and argument-hint, revise those fields

Conclusion: Build the Commands You Already Type

Claude Code slash commands turn ad-hoc prompting into a versioned, shareable part of your repo. Start by saving the three prompts you already type most often — reviews, commit messages, and test scaffolds are the classic first three — and let the library grow as you notice new patterns. Every command you save is a prompt you will never reconstruct from memory again.

Next, pair slash commands with automated lifecycle triggers. Our Claude Code hooks tutorial shows how to wire linting, testing, and commit gating into the events Claude Code emits — the natural complement to the on-demand workflows you build with slash commands.

Leave a Comment