Custom Slash Commands in Claude Code: Templates You Can Copy

A custom command is one Markdown file that becomes a /name you can type. Where the files go now that commands have merged into skills, how arguments work, five command files to copy, and when a command should become a skill instead.

7 min read

A Claude Code custom command is a Markdown file whose name becomes a command: save a prompt as .claude/commands/fix-issue.md and typing /fix-issue 123 sends that prompt with 123 filled in. Put the file in the repository’s .claude/commands/ for the team, or in ~/.claude/commands/ for yourself in every project. Custom commands are now part of skills, so the same file could equally live at .claude/skills/fix-issue/SKILL.md; the single-file form still works and is the quickest way to save a prompt you keep typing. Below: what changed, where the files go, how arguments work, five files to copy, and the point at which a command should become a skill.

What a custom command is now

Custom commands used to be a feature of their own. The Claude Code skills documentation (opens in a new tab) now says plainly that they have been merged into skills: a file at .claude/commands/deploy.md and a skill at .claude/skills/deploy/SKILL.md both create /deploy and work the same way, and existing command files keep working. Three consequences matter when you write one:

  • A command file accepts the same frontmatter as a skill, except name and paths. The file name is the command name.
  • If a skill and a command file share a name, the skill runs.
  • Claude can start a command on its own when your request matches its description, just as it can a skill. For anything with side effects, add disable-model-invocation: true so only you can start it.

How commands sit beside subagents, hooks, MCP servers and plugins is a separate question, answered in Claude Code plugins vs skills vs subagents. This article stays with the file you type.

Where the files live

  • Project: .claude/commands/<name>.md. Commit it and everyone who works in the repository gets /name.
  • Personal: ~/.claude/commands/<name>.md, which the .claude directory reference (opens in a new tab) describes as the same as project commands but scoped to your user account, available in every project.
  • Subfolders namespace the name: .claude/commands/frontend/component.md becomes /frontend:component.
  • A directory added with --add-dir brings its .claude/commands/ too, but those files are not watched, so restart the session after changing one.
  • Plugins can carry commands as well, and theirs run under the plugin’s name, as /plugin-name:command, so they never replace yours.

The frontmatter worth knowing

Every field is optional; a file with no frontmatter at all is a working command. These are the ones that earn their place in a command file:

  • description: what the command does and when to use it. Without one, the first line of the body is used.
  • argument-hint: shown during autocomplete, such as [issue-number].
  • disable-model-invocation: true: only you can start it. Use it for anything that commits, deploys or sends.
  • allowed-tools: tools Claude may use without asking, for the turn that invoked the command only. Workspace trust does not gate it, so read this field in any command file someone else committed.
  • model and effort: override the session’s model or effort level while the command runs.
  • context: fork with agent: run the command in a subagent instead of your conversation.

Arguments

Whatever you type after the command name is available to the file through placeholders. $ARGUMENTS is the whole string as typed. $ARGUMENTS[N] is one word by position, counting from zero, and the shorthand $N means the same, so the first word is the dollar sign followed by 0. Quote a multi-word value to keep it together: /review-file "src/cart/total.ts" rounding gives $ARGUMENTS[0] the path and $ARGUMENTS[1] the word rounding. You can also name them in the frontmatter with arguments: [file, focus] and write $file and $focus in the body.

Two behaviours catch people out. If the file has no placeholder at all, Claude Code appends ARGUMENTS: <what you typed> to the end, so the input is never lost. And an indexed placeholder with nothing at its position, such as $ARGUMENTS[2] when you passed two words, stays in the text literally, while a named one becomes an empty string. Add argument-hint: [issue-number] to the frontmatter and the hint appears as you type the command.

Five command files to copy

Each of these is a complete file. Save it under the path in its caption and it is available as soon as Claude Code picks it up.

1. Fix an issue by number

.claude/commands/fix-issue.md
---
description: Fix a GitHub issue by number, with a test that proves it.
argument-hint: [issue-number]
disable-model-invocation: true
---

Fix issue #$0.

1. Run `gh issue view $0` and restate the problem in one sentence.
2. Write a failing test that reproduces it. Run it and show it fail.
3. Make the smallest change that makes the test pass.
4. Run the full test suite. Do not commit.
5. Summarise: the cause, the change, and the test you added.

disable-model-invocation keeps Claude from deciding on its own to start fixing issues. The steps end before a commit on purpose; the next command does that part.

2. Commit with the diff already in the prompt

.claude/commands/commit.md
---
description: Stage and commit the current changes with a conventional message.
disable-model-invocation: true
allowed-tools: Bash(git add *) Bash(git commit *) Bash(git status *) Bash(git diff *)
---

## Current state
!`git status --short`

## Staged diff
!`git diff --cached`

Write one commit message in the form "type(scope): summary", under 72
characters, then a blank line and at most three lines of detail.
If nothing is staged, stage only the files listed above that belong to
one change, and say which you left out. Then commit.

A line that starts with ! followed by a command in backticks runs before Claude sees the file, and the command’s output replaces it, so Claude reads the real diff rather than guessing. allowed-tools lets the listed git commands, including the two injected ones, run without a prompt, and only for the turn that invoked the command.

3. Review one file for one concern

.claude/commands/review-file.md
---
description: Review one file for a single concern, without editing it.
argument-hint: [file] [concern]
arguments: [file, focus]
---

Review $file for $focus only.

- Quote the line number for every finding.
- Rank findings: must fix, should fix, note.
- Do not edit the file. Do not comment on style unless $focus is style.
- If you find nothing, say so in one line.

Run it as /review-file src/cart/total.ts rounding. Named arguments read better than numbered ones once a command takes more than one.

4. Explain a module in a separate context

.claude/commands/explain.md
---
description: Explain how a module works, from the code, in under 300 words.
argument-hint: [path]
context: fork
agent: Explore
---

Read $ARGUMENTS and the files it imports. Explain:
1. What it is for, in one sentence.
2. The main entry points and what calls them.
3. Anything surprising: global state, hidden I/O, order dependencies.
Quote file paths. Do not suggest changes.

context: fork runs the command in a subagent, here the read-only Explore agent described in the subagents documentation (opens in a new tab), so the file reading happens in its own context and only the explanation comes back to yours. The subagent does not see your conversation, which is why the instructions spell everything out.

5. File a task on the board

.claude/commands/task.md
---
description: Record something as a task on the fenbs board.
argument-hint: [what needs doing]
disable-model-invocation: true
---

Record this on the board: $ARGUMENTS

1. Call fenbs_search with the key words. If a matching task exists,
   add what I said to it with fenbs_comment and stop.
2. Otherwise call fenbs_create_item in the project named in CLAUDE.md.
   Kind: bug if something is broken, feature if it is new,
   enhancement if it changes something that works.
3. Reply with the task's reference and title.

This one assumes a fenbs board is connected over MCP, which is one claude mcp add command on the Claude Code integration page. Mid-session you notice a second bug, type /task payment retries log the wrong order id, and it lands on the board as a task instead of a line in the chat that scrolls away. The board checks the call against the connection’s role and scopes and records the new task under the assistant’s name. How the rest of the session reads and updates tasks is in a task-tracking workflow for Claude Code.

When a command does not work

  • It is not in the / menu: check the file is directly in commands/ or a subfolder of it, ends in .md, and that no skill of the same name is hiding it.
  • It runs but ignores its settings: the frontmatter is only read when the opening --- is the very first line of the file. If the YAML does not parse, the body still loads with no fields set; start with claude --debug to see the error.
  • It fails with “Shell command failed for pattern”: an injected ! command exited non-zero, which aborts the whole command. Append || true to a command that is allowed to fail.
  • An injected command is refused: these never prompt. Outside auto mode, a command your permission rules (opens in a new tab) do not already allow aborts the invocation, so list it in allowed-tools.

When to make it a skill instead

The single file is enough while the command is one prompt. Move it to .claude/skills/<name>/SKILL.md when any of these become true:

  • It needs files beside it: a script to run, a style guide, examples. Only a skill folder can carry them, and ${CLAUDE_SKILL_DIR} finds them wherever the session started.
  • It should load only when Claude works on certain files. That is the paths field, which command files do not accept.
  • It has grown past a screenful. Once invoked, the body stays in the conversation, so detail belongs in reference files Claude reads when needed.
  • You want Claude to reach for it without being asked. Commands can be picked up by description too, but a skill is the form built around a good description.

The step-by-step, including descriptions that trigger and how to test that they do, is in how to create a Claude skill. The move itself is mechanical: make the folder, rename the file to SKILL.md, and the command name stays the same.

Related

Every built-in command in one place: Claude Code commands cheat sheet. Where commands/ sits beside settings, rules and agents in a repository: Claude Code project structure. Connect a board for command five: Claude Code integration.

Questions people ask.

Are custom slash commands deprecated in Claude Code?

No. They have been merged into skills, and files in .claude/commands/ keep working and create the same /name. New work is usually better as a skill folder, because it can carry supporting files and more frontmatter options.

Where do I put a custom command for every project?

In ~/.claude/commands/<name>.md. A file in the repository’s .claude/commands/ applies only to that project, and committing it shares it with everyone who works there.

How do I pass arguments to a custom command?

Use $ARGUMENTS for everything typed after the command, or $ARGUMENTS[N], or the shorthand $N, for single words by position, counting from zero. You can also declare names with the arguments frontmatter field and write $name in the body. Quote a multi-word value to keep it as one argument.

Can Claude run my custom command without me typing it?

Yes, if your request matches the command’s description. Add disable-model-invocation: true to the frontmatter for commands with side effects, such as committing or deploying, so they only run when you type them.

Start with one thing.

There is nothing to set up first. Write one line and you’ve started.