knack
← all posts

Claude Code Slash Commands: How to Build Your Own Custom Commands

Where command files live, the file format with a worked example, arguments and frontmatter, namespacing, and when a slash command is the wrong tool.

You paste the same three-paragraph prompt into Claude Code every morning. "Look at my uncommitted changes, write a conventional-commit message, flag anything risky." Every morning. That prompt should be a key you press, not a thing you retype. Claude Code slash commands turn a repeated prompt into a typed shortcut like /commit, and you can build your own in about four minutes with a single Markdown file.

This is a how-to. By the end you'll know where the files live, the format, how to pass arguments, and what each frontmatter field does. You'll also know the one situation where a slash command is the wrong tool, and you should reach for a skill instead.

One thing up front, because the docs moved in mid-2026. Anthropic merged custom commands into skills. A file at .claude/commands/deploy.md and a skill at .claude/skills/deploy/SKILL.md both produce /deploy and behave the same way when you invoke them. Your existing .claude/commands/ files keep working, frontmatter and all. So everything below is accurate, and where the skill format adds something the command format can't do, I'll say so.

Built-in commands vs custom slash commands

Type / in any Claude Code session and a menu drops down. Most of what you see is built in. /clear starts a fresh conversation, /compact summarizes a long one to free context, /model switches models, /init writes a starter CLAUDE.md, /review reviews a pull request, /permissions edits your approval rules. These execute fixed logic that ships with the CLI. You can't edit them, and the full reference runs to well over a hundred entries.

A handful of those entries are marked Skill in the docs, like /code-review, /debug, /loop, and /verify. Those are prompt-based. Anthropic wrote the instructions, and Claude orchestrates the work with its tools. That matters because it's the same machinery your custom commands use. When you write a /commit command, you're building the same kind of thing the /code-review skill is. It's just scoped to you.

Custom commands are the ones you author. They sit in a folder, they're plain Markdown, and the filename becomes the command. Want a /standup command? Create standup.md. That's the whole naming convention.

A quick aside on getting around the menu: the keyboard shortcuts and the / menu post covers how to filter and fire commands fast once you have a few. Typing /com and hitting tab beats scrolling.

Where command files live

Two locations, and the difference is who gets the command.

Project commands go in .claude/commands/ at the root of your repo. Commit that folder and every teammate who clones the repo gets the same commands. This is how you ship a /deploy or /run-migrations that encodes how this project works. It's also the answer to the common search "claude code slash commands github": put the command files in the repo, push, and they travel with the code.

Personal commands go in ~/.claude/commands/ in your home directory instead. These follow you across every project on your machine, and nobody else sees them. My /commit lives here because I want it everywhere, in every repo, without committing it to other people's codebases.

If you'd rather use the newer skill format, the parallel paths are .claude/skills/<name>/SKILL.md for project and ~/.claude/skills/<name>/SKILL.md for personal, where the directory name (not the filename) becomes the command. When a skill and a command share a name, the skill wins. For a plain prompt-in-a-file, the .claude/commands/ route is fewer keystrokes, so that's what I'll use for the worked example.

The file format, with a worked example

Here's a real one. Save this as ~/.claude/commands/commit.md:

---
description: Stage changes and write a conventional-commit message
allowed-tools: Bash(git add:*), Bash(git status:*), Bash(git commit:*)
argument-hint: [optional scope]
---

## Current state
- Status: !`git status --short`
- Diff: !`git diff HEAD`

## Task
Stage the relevant changes and write a single conventional-commit
message. Use the scope "$ARGUMENTS" if one was provided. Flag anything
risky you notice: hardcoded secrets, missing error handling, a debug
print left behind.

Type /commit and a few things happen in order. The two !`...` lines run first, as preprocessing, before Claude reads anything. Claude Code executes those shell commands and pastes their output into the prompt in place of the placeholder. Claude arrives already holding your actual git status and diff, and then it follows the task. The description is what shows up next to /commit in the menu.

That inline ! only fires when it sits at the start of a line or right after whitespace. Write KEY=!cmdand it stays literal text, the command doesn't run. For something multi-line, open a fenced block with ```! `` instead.

You can also pull a file's contents in with @, the same way you reference files in a normal Claude Code message. Review @src/auth.ts for injection bugs inlines that file.

Passing arguments

Most commands need input. A /fix-issue command needs to know which issue.

$ARGUMENTS is the catch-all. Whatever you type after the command name lands there. Put Fix GitHub issue $ARGUMENTS following our standards in the file, run /fix-issue 482, and Claude sees Fix GitHub issue 482 following our standards. If your command has no $ARGUMENTS placeholder at all and you pass input anyway, Claude Code appends it as ARGUMENTS: <your text> so nothing gets dropped.

For positional access, use $ARGUMENTS[0], $ARGUMENTS[1], and so on, zero-indexed. There's a shorthand too: $0 is the first argument, $1 the second. A migration command might read Migrate the $0 component from $1 to $2, and /migrate Button React Vue fills them in. Quoting works like a shell, so /migrate "search bar" React Vue keeps search bar as one argument.

The skill format adds named arguments on top of this. Declare arguments: [issue, branch] in frontmatter and then write $issue and $branch in the body instead of counting positions. Handy once a command takes more than two inputs and $2 starts meaning nothing to whoever reads the file later.

Frontmatter options

The block between the --- markers is optional, but four fields earn their place.

description is the one to always set. It's the menu label, and if you ever let Claude trigger the command automatically, it's how Claude decides when. argument-hint shows expected input during autocomplete, like [issue-number] or [filename] [format]. It's cosmetic. Think of it as a note to your future self about what shape of input the command wants.

allowed-tools is the one that changes behavior. List tools here and Claude can use them without stopping to ask you for approval each time the command runs. The /commit example grants the three git operations it needs, so the command runs start to finish without a permission prompt on every git add. It grants, it doesn't restrict. Every other tool is still available, governed by your normal permission settings. For a project command checked into a repo, those grants only take effect after you accept the workspace trust dialog, so review what a shared command hands itself before you trust the folder.

model pins the command to a specific model regardless of your session model. A trivial reformatting command can run on a small fast model; you don't need your heavyweight reasoning model to rewrite a commit message.

Namespacing with subdirectories

Once you have a dozen commands, group them. Subdirectories under .claude/commands/ namespace the command name. Put a file at .claude/commands/frontend/component.md and the command becomes /frontend:component. The folder is the prefix, the file is the command. This keeps /frontend:component and /backend:migrate from colliding and makes the / menu readable when it's full. Plugins use the same idea automatically: a command from a plugin shows up as /plugin-name:command, so nothing a plugin ships can stomp on a command you wrote.

When a slash command is the wrong tool

A slash command is a one-liner you fire on purpose. You type /commit, it runs, done. That's exactly right for a prompt you reuse and want to trigger yourself.

It starts to strain in three situations. The first is when the procedure outgrows a single file. A real deploy runbook has a checklist, a rollback script, an example of a good release note, maybe a reference doc on your infra. A flat .md command can't carry attachments. A skill can, because it's a directory, so it bundles supporting files, scripts, and reference material that load only when needed.

The second is when you want Claude to reach for it on its own. A bare command in .claude/commands/ only runs when you type it. If you want Claude to notice "the user is asking about their diff" and load the right instructions without being told, you need a skill with a description that says when to use it. That auto-invocation is the line between a command and a skill, and it's deliberate. Some things, like /deploy, you never want Claude deciding to run on its own.

The third is when the workflow has to be right every time and survive being handed to someone else. A prompt in a file drifts. The procedure lives in your head, the file captures a slice of it, and six months later nobody remembers why step three matters. This is the durable version of the same idea, and it's what Knack is built for. You talk through a workflow in a 20-minute interview and it writes a portable Agent Skill in the Anthropic SKILL.md format. The slash command is the sticky note. The skill is the documented procedure that runs the same way in Claude Code, Codex, Cursor, or Gemini CLI, for anyone on the team.

For the full decision tree across all four extension types, the skills vs subagents vs slash commands vs plugins breakdown lays out which one fits which job.

Start with one

Pick the prompt you've typed twice this week. Drop it in ~/.claude/commands/ with a one-line description, add $ARGUMENTS if it needs input, and use it tomorrow. When you catch yourself wishing it could pull in a checklist file or run without your typing it, that's the signal to graduate it to a skill, and Knack will turn the interview into the SKILL.md for you. The first /commit you write changes how your mornings go.