Custom Slash Commands in Claude Code (They're Skills Now)

Stop retyping the same elaborate prompt. Capture it once as a slash command and invoke it forever — with arguments, live context injection, and control over who can trigger it.

If you find yourself typing the same elaborate instruction to Claude Code over and over — "review this diff for security issues, checking auth, input validation, and secrets, and format the findings as a list" — you're doing manual work the tool is built to eliminate. You can capture that instruction once as a custom slash command and invoke it forever with /security-review. Here's how, and the modern shape of the feature.

Commands are now skills

First, an important clarification, because the docs have evolved: custom slash commands have merged into Skills. The legacy form — a single markdown file at .claude/commands/deploy.md — still works and still creates /deploy. But the modern, recommended form is a skill: a directory .claude/skills/deploy/ containing a SKILL.md file. Both produce a /deploy command; the skill version just does more. So everything below is written in the skill form, which is where the feature is actively developed.

Creating one

A skill is a markdown file with YAML frontmatter and a body:

---
description: Review the current diff for security issues
argument-hint: [severity]
allowed-tools: Bash(git diff:*) Read
---

Review the staged diff for security problems. Check authentication,
input validation, and hardcoded secrets. Report findings as a list
ordered by severity.

Save that as .claude/skills/security-review/SKILL.md and you have /security-review. Two things worth noting immediately:

  • The command name comes from the directory, not the frontmatter. .claude/skills/security-review/SKILL.md/security-review. The name/description fields control how it's labeled and when Claude might auto-invoke it, not the command string.
  • The body becomes the prompt. Everything after the frontmatter is what Claude receives when you invoke the command.

Skills live at two scopes: .claude/skills/ (project, shared with your team via git) and ~/.claude/skills/ (personal, available across all your projects).

Arguments

Commands get more useful when they take input. Claude Code substitutes several variables into the body before running it:

  • $ARGUMENTS — everything you passed, as one string.
  • $1, $2, … — positional arguments.
  • Named arguments, if you declare them in frontmatter.

So a skill body containing Migrate the $1 component from $2 to $3 invoked as /migrate SearchBar React Vue becomes "Migrate the SearchBar component from React to Vue." Multi-word arguments use shell-style quoting: /my-cmd "hello world" second puts hello world in $1.

Injecting live context

This is the feature that turns a static prompt into a dynamic tool. Inside a skill body, !`<command>` runs a shell command before the content reaches Claude, and substitutes the output in place. So:

## Current diff
!`git diff --staged`

## Task
Review the diff above for security issues.

When you run this, Claude Code executes git diff --staged first and Claude receives the actual diff text, not the command. Your slash command now always operates on live data. (One gotcha: this injection runs once — the output isn't re-scanned for further !` placeholders.)

Two ways to invoke — and controlling that

Because commands are now skills, there are two invocation paths:

  1. You type it/security-review, from the / menu.
  2. Claude auto-invokes it — if a skill's description matches what you're asking for, Claude can reach for it on its own. That's powerful for knowledge-type skills but not what you want for something with side effects.

For anything that acts on the world (deploys, commits, destructive operations), set disable-model-invocation: true so only you can trigger it — Claude won't fire it unprompted. Conversely, user-invocable: false hides a skill from the / menu so it only serves as background knowledge Claude pulls in automatically. This split — model-invoked vs user-invoked — is the key mental model that distinguishes skills from the old commands.

Organizing them

Subdirectories create namespaced commands: .claude/skills/ops/deploy/SKILL.md becomes /ops:deploy. This keeps a large collection tidy and avoids name clashes. If a nested skill shares a name with a root one, both stay available (/deploy and /apps/web:deploy), and typing the bare name loads the root version.

The payoff

The reframe worth internalizing: a slash command is a prompt you refuse to retype. Any instruction detailed enough that you've written it more than twice — a review checklist, a release procedure, a specific refactoring pattern, a report format — is a candidate to become a command. You write it carefully once, with the exact tools it's allowed to use and the exact context it should pull in, and from then on it's a single slash away, executed consistently every time instead of approximated from memory. That consistency is the real win: the command doesn't just save keystrokes, it removes the variance of you remembering to ask for everything you wanted.


Related reading