0%
#slash-commands#claude-code#customization#tutorial#guide

Custom Slash Commands in Claude Code: A Complete Guide

Published 2026-04-2310 min read

[01]Why Write Custom Slash Commands

If you keep pasting the same prompt — "review this diff," "write a commit message," "explain this module" — you are already describing a slash command. Write it once in .claude/commands/ and you get three wins: tab-completion, team-shareable via git, and consistent output because the prompt is fixed.

Custom commands also serve as lightweight guardrails. A command with allowed-tools set to Read-only can enforce "review, do not modify" at the harness level, not the model level. That is a stronger guarantee than "please don't change anything" in the prompt.

This guide covers the file anatomy, every frontmatter field that matters, the three argument passing styles, six copy-paste templates, and the scope rules that decide who runs your command.

[02]Anatomy of a Command File

A slash command is a Markdown file under .claude/commands/ (project) or ~/.claude/commands/ (user). Filename becomes the command name — review.md/review. Subdirectories use : as separator — git/commit.md/git:commit.

Structure:

---
description: One-line summary shown in /help and autocomplete
allowed-tools: Read, Grep, Glob, Bash(git diff:*)
argument-hint: <file-path or branch>
model: sonnet
---

Your prompt template goes here. It becomes the user message sent to
Claude when the command runs.

Use $ARGUMENTS to interpolate whatever the user typed after the command.

Everything above the second --- is YAML frontmatter. Everything below is the prompt body sent verbatim to Claude, with $ARGUMENTS substituted for whatever the user typed after the command name.

[03]Frontmatter Fields Reference

FieldRequiredWhat it does
descriptionYesShows in /help, autocomplete tooltip, and team docs
allowed-toolsNoRestrict which tools Claude can use. Tighter than session-wide permissions
argument-hintNoAutocomplete hint shown after the command name (e.g. <file>)
modelNoForce a specific model for this command (opus, sonnet, haiku)
disable-model-invocationNoPrevent the AI from auto-calling this command — user must type it explicitly

allowed-tools Syntax

Takes a comma-separated list of tool names, with optional patterns in parentheses for Bash:

allowed-tools: Read, Grep, Glob, Bash(git diff:*), Bash(git log:*)

Anything outside this list triggers a permission prompt even if the session-wide config would auto-approve. Use this to lock down destructive commands inside high-trust command templates.

[04]Passing Arguments

Three styles, from simplest to most powerful:

Style 1: Plain $ARGUMENTS

Everything after the command name goes into $ARGUMENTS verbatim.

// .claude/commands/explain.md
---
description: Explain a file in plain English
argument-hint: <file-path>
---

Read the file at $ARGUMENTS and produce a 5-sentence plain-English
explanation aimed at a new team member.

Usage: /explain src/auth/session.ts

Style 2: Positional $1 $2

Reference individual space-separated tokens. Skipped if your version does not support them — fall back to parsing $ARGUMENTS.

---
description: Compare two git branches on a specific path
argument-hint: <branch-a> <branch-b> [path]
---

Compare git branch $1 against $2, focusing on the path $3 if provided.
Summarize the diff in three bullet points.

Usage: /compare-branches feature/auth main client/src/auth/

Style 3: Embedded Bash with !

Prefix a line with ! to run bash at expansion time and splice the output into the prompt. Useful for grounding the prompt in real repo state.

---
description: Draft commit message from current staged changes
allowed-tools: Bash(git diff:*), Bash(git status)
---

Current git status:
!`git status --short`

Staged diff:
!`git diff --cached`

Draft a conventional commit message under 72 chars.

This runs the bash commands first, then hands Claude a prompt pre-populated with real state. No ambiguity about "what's staged."

[05]Six Copy-Paste Templates

1. /review — PR review against main

---
description: Review current branch vs main
allowed-tools: Read, Grep, Glob, Bash(git diff:*), Bash(git log:*)
argument-hint: <focus-area optional>
---

Compare the current branch to main.
!`git diff main...HEAD --stat`

Focus: $ARGUMENTS (default: security, performance, test coverage)

Report findings as a numbered list with file:line references.
End with a PASS/FAIL verdict.

2. /commit — draft conventional commit

---
description: Draft a conventional commit message from staged changes
allowed-tools: Bash(git diff:*), Bash(git status)
---

Current staged changes:
!`git diff --cached`

Draft a single-line conventional commit (< 72 chars) plus an optional
two-line body. Do not run git commit.

3. /explain — plain-English walkthrough

---
description: Explain a file or symbol in plain English
allowed-tools: Read, Grep
argument-hint: <file-path>
---

Read $ARGUMENTS and produce a 5-sentence explanation for a developer
new to this codebase. Avoid jargon. Name the three most important
functions and what calls them.

4. /doc — generate JSDoc / TSDoc

---
description: Add JSDoc to an undocumented function
allowed-tools: Read, Edit
argument-hint: <file:line>
---

Open the file in $ARGUMENTS and add TSDoc comments to every exported
function. Keep comments under 3 lines. Follow the existing style in the
file.

5. /fix-types — resolve TypeScript errors

---
description: Run typecheck and fix errors
allowed-tools: Read, Edit, Write, Bash(npm run check), Grep
---

!`npm run check 2>&1 | head -60`

Fix every TypeScript error above with the minimal possible change.
Do not add `any`. Do not widen types. If a fix is non-obvious, leave
it and report what is stuck.

6. /deploy — preview deploy (read-only metadata)

---
description: Show deploy metadata for the current branch
allowed-tools: Bash(git branch:*), Bash(git rev-parse:*), Read
argument-hint: <env: preview|production>
---

Branch: !`git branch --show-current`
Commit: !`git rev-parse HEAD`
Environment requested: $ARGUMENTS

Summarize what would deploy and list any pending migrations. Do NOT
run the deploy — only describe what would happen.

[06]Project vs User Scope

Slash commands resolve from two locations:

ScopeLocationUse for
Project.claude/commands/Team-shared, committed to git
User~/.claude/commands/Your personal shortcuts across all projects

When names collide, project wins — that way a team can lock down /deploy with the approved template regardless of what an individual has globally. If you want your personal preferences alongside team commands, use different names (/my-review vs /review).

Namespacing with Subdirectories

Place related commands in a subdirectory and invoke them with : notation:

.claude/commands/
  git/
    commit.md        → /git:commit
    review.md        → /git:review
  test/
    unit.md          → /test:unit
    e2e.md           → /test:e2e

Keeps /help readable once you pass ten custom commands.

[07]Next Steps

You have commands running. What compounds them:

Write a command worth sharing? Start from the checker and drop it in a blog comment.

[08]Frequently Asked Questions

Can I use custom commands to call other custom commands?

Not directly — slash commands are expanded into a single prompt, they do not recursively invoke each other. If you need composition, create a sub-agent (see .claude/agents/) and delegate. The agent can in turn use any command it has access to.

What happens if my command body has $ in it (not as a variable)?

Escape it by writing $$ or, in bash embedded sections, use \$. The expansion engine treats $ARGUMENTS and $1..$9 as special; everything else should pass through, but prefer to escape when mixing shell commands with prompt text.

Do commands respect session-wide permissions or override them?

allowed-tools in a command is a restriction, not an expansion. If your session already denies Bash(rm *), no command can grant it. If your command declares allowed-tools: Read, that is the ceiling — even if the session allows more.

Can I ship commands as a package for others to install?

There is no official package format as of 2026 Q1. Teams share commands by committing them to the project .claude/commands/ or by publishing a GitHub repo others clone into ~/.claude/commands/. Some community tooling proposes npm-style distribution — watch the Claude Code GitHub for a canonical spec.

Why does my !`command` bash execution not run?

Two common causes: (1) the command you invoked is not in allowed-tools — explicit Bash allowlist required. (2) You wrote !`cmd` on a line that has content before the !; the expansion only triggers when ! is the first non-whitespace character. Fix: put the bash line on its own line, no prefix.

// COMMENTS

github_discussions.sh

Sign in with GitHub to leave a comment.

Ready to find your buddy?

CHECK YOUR BUDDY

Built by the community. Not affiliated with Anthropic.

All computation is local. No data is collected or transmitted.

> EOF