Writing Your First Custom Slash Command with Markdown Frontmatter
A custom slash command is a saved prompt you invoke by name instead of retyping.
Search across all documentation pages
A custom slash command is a saved prompt you invoke by name instead of retyping.
It is stored as a plain markdown file with a frontmatter header and a prompt body underneath.
A command file has exactly two parts: YAML frontmatter between --- markers, and a prompt body below it.
The frontmatter carries metadata: a short description, and optionally which tools the command is allowed to use.
The body is the literal text sent into the conversation the moment someone types /command-name.
Commands are scoped either to a project, at .claude/commands/, or to a user, so they are available across every project that user opens.
Because a command is just a file, creating one and sharing one both reduce to normal git operations, no separate registration step exists.
---
description: One-line summary shown in command autocomplete
---
The prompt text that gets sent when this command is invoked. Write it exactly
as you would type the request yourself.Save this at .claude/commands/<command-name>.md and it becomes available as /<command-name> in the very next prompt, no restart needed.
When to reach for this:
$ARGUMENTS.---
description: Review a pull request for common issues before requesting a human reviewer
allowed-tools: Bash(git diff:*), Bash(git log:*), Read, Grep
---
Review the current branch's changes against main before I request a human reviewer.
1. Run `git diff main...HEAD` to see everything that changed.
2. Check for: missing error handling, leftover debug logging, and any TODO comments
that should be resolved before merge.
3. Confirm the changes match what the branch name or last commit message implies.
4. Report findings as a short bullet list, ranked by severity. If nothing stands out,
say so plainly rather than inventing minor nitpicks.Save this as .claude/commands/pre-review.md. Typing /pre-review runs the checklist against whatever branch is currently checked out.
What this demonstrates:
description frontmatter surfaces in autocomplete so teammates can discover the command without opening the file.allowed-tools scopes down what the command can do, here restricting git access to read-only diff and log operations plus file reading.$ARGUMENTS is used here; the command always operates on "whatever branch is currently checked out," which is a valid, argument-free command shape./command-name.| Field | Required | Purpose |
|---|---|---|
description | Recommended | One-line summary shown in command autocomplete and listings. |
allowed-tools | Optional | Restricts which tools this command's prompt is permitted to use, narrower than the session default. |
argument-hint | Optional | A short hint shown in autocomplete describing what $ARGUMENTS expects, e.g. <file-path>. |
model | Optional | Pins this command to a specific model instead of inheriting the session's current model. |
---
description: Explain what a specific file does and who depends on it
argument-hint: <file-path>
---
Read $ARGUMENTS and explain its purpose, its public exports, and any files
in this repo that import from it. Keep the answer under 150 words.$ARGUMENTS is replaced with everything typed after the command name, so /explain-file lib/auth.ts sends lib/auth.ts in place of $ARGUMENTS.$ARGUMENTS resolves to an empty string, so word the prompt so it still makes sense (or explicitly states a target is required).argument-hint is purely cosmetic. It shows the reader what to type but does not validate or enforce anything at invocation time.---
description: Keep frontmatter minimal - only fields the command actually uses
---
Prefer a short, direct body over a long one. The prompt body is read every
single time the command runs, so verbosity here is a recurring cost, not a
one-time cost.--- fences; unknown fields are typically ignored rather than causing an error, but stick to documented fields to avoid surprises.description field - the command still works, but it becomes much harder for teammates to discover what it does from autocomplete alone. Fix: always include a one-line description, even for commands you wrote just for yourself.$ARGUMENTS as required - if the prompt body assumes a target was always passed and one wasn't, the command runs against an empty string and produces a confusing result. Fix: explicitly instruct the prompt to ask for a target, or state a default behavior, when $ARGUMENTS is empty..claude/commands/ for existing names before adding a personal one.allowed-tools - leaving this unset means the command inherits the full session's tool access, even for a command that only ever needs to read files. Fix: scope allowed-tools down to exactly what the command's steps require.$ARGUMENTS; it is a raw string substitution. Fix: write the prompt defensively, telling Claude what to do if the argument looks malformed or missing.| Alternative | Use When | Don't Use When |
|---|---|---|
| Typing the request fresh each time | The request is truly one-off and unlikely to repeat. | You catch yourself typing a near-identical prompt more than twice. |
| A PostToolUse or PreToolUse hook | The action must happen automatically and deterministically, with no judgment involved. | The task requires reasoning, context-gathering, or a decision, since hooks cannot reason. |
| A subagent spawned ad hoc | The task is a one-time, isolated research or exploration job. | The same delegation pattern will be needed repeatedly, which is better captured as a command that spawns a subagent. |
No. Claude Code discovers commands by scanning the commands directory; creating the markdown file is the entire setup step.
.claude/commands/ inside a repo and are shared with anyone who checks that repo out.Yes. The body is plain text sent into the conversation, so any markdown formatting, including code fences and numbered lists, is preserved and interpreted normally.
$ARGUMENTS resolves to an empty string. The prompt still runs, so it should be worded to handle that case gracefully rather than assuming a value is always present.
Yes, via the allowed-tools frontmatter field, which scopes tool access down for that command's execution, independent of the broader session's permissions.
There is no hard documented limit, but a long body is sent in full every time the command runs, so concise prompts are cheaper and easier to maintain than sprawling ones.
A command's prompt body can instruct Claude to perform the same steps another command would, but commands do not directly invoke one another the way a function calls a function; the body is just text.
No. The file is read at invocation time, so edits take effect on the very next time the command is called.
Set the description frontmatter field to a clear one-line summary; it is what shows up in autocomplete when a teammate is browsing available commands.
Yes, using the model frontmatter field, which overrides the session's current model just for that command's invocation.
No. It is a raw string substitution with no schema, so any validation of the argument's shape or content has to be written into the prompt itself.
Not necessarily. A command is worth the overhead once a request repeats often enough that saving and naming it pays for itself; a truly one-off request is simpler typed fresh.
Stack versions: Written against the Claude model lineup current as of ~June 2026 - Claude Fable 5, Claude Opus 4.8, Claude Sonnet 5 (the default), and Claude Haiku 4.5. Model names, pricing, and product features move quickly - verify current specifics at platform.claude.com/docs before relying on them.
Reviewed by Chris St. John·Last updated Jul 16, 2026