Best Practices for Implementing Slash Commands in Claude Plugins: A Complete Guide
Slash commands in Claude plugins are Markdown files stored in commands/ directories that use YAML frontmatter to declare permissions, descriptions, and tool restrictions, making them discoverable via /help and safe to execute.
The anthropics/claude-plugins-official repository defines the standard architecture for creating reusable, discoverable functionality in Claude Code. These commands allow developers to package complex workflows into simple /command-name invocations that users can execute across personal, project, and plugin contexts.
Directory Structure and File Locations
Claude auto-discovers slash commands from three specific locations based on scope. According to plugins/plugin-dev/skills/plugin-structure/SKILL.md, the system checks these directories for .md files:
- Project-wide commands:
.claude/commands/(shared via version control with the entire team) - Personal commands:
~/.claude/commands/(available across all projects for the current user) - Plugin-bundled commands:
<plugin-name>/commands/(distributed and installed with a specific plugin)
Use kebab-case for file names (e.g., review-code.md). The file name becomes the command trigger, so review-code.md automatically registers as /review-code in the slash command picker. No additional registration or configuration files are required—simply placing the Markdown file in the correct directory enables the command.
YAML Frontmatter Configuration
Every production command should include a minimal but complete YAML frontmatter block. As documented in plugins/plugin-dev/skills/command-development/SKILL.md, the frontmatter controls discoverability, permissions, and execution behavior.
Essential Fields
The description field appears in /help output and must stay under 60 characters for optimal display. This text determines whether users can discover your command when searching available tools.
The allowed-tools field implements a whitelist approach to security. Enumerate only the specific tools your command requires, such as Read, Glob, or Bash(git:*). This restriction reduces permission prompts and prevents accidental tool abuse by limiting Claude's access during command execution.
Optional Control Fields
Use argument-hint to document expected input format (e.g., <commit-message>). This hint displays in the UI when users invoke the command.
Set model only when a specific model is required (e.g., haiku for fast, low-cost operations). Otherwise, omit this field to inherit the conversation's active model.
For dangerous operations like destructive Bash scripts, set disable-model-invocation: true. This flag prevents Claude from running the command automatically during autonomous execution, requiring explicit manual confirmation.
Security and Permission Management
Security implementation in slash commands centers on tool-level argument filtering and input sanitization. The plugins/security-guidance/hooks/security_reminder_hook.py enforces runtime checks that prevent command injection attacks.
Never interpolate untrusted user input directly into shell commands. Instead, use the tool-level filtering syntax to restrict Bash operations to specific command patterns. For example, Bash(git:add*,git:commit*,git:status*) limits execution to only git subcommands matching those prefixes.
Keep command bodies pure instructions for Claude. Avoid user-facing prose or conversational text in the command body—this content executes as instructions to the AI, not as documentation for the user.
Dynamic Arguments and User Input
Commands accept dynamic input through the $ARGUMENTS variable. When a user types /command-name some input here, the string "some input here" replaces $ARGUMENTS in the command template.
For structured argument parsing, use $ARGUMENT_1, $ARGUMENT_2, etc., to capture positional parameters individually. Document the expected argument format in the argument-hint frontmatter field so users understand the required input structure when invoking the command.
Testing and Validation Strategies
Before distributing a command, validate it using the testing strategies outlined in plugins/plugin-dev/skills/command-development/references/testing-strategies.md. Create a local copy in .claude/commands/ and verify:
- The command appears in
/helpwith the correct description - The command executes without permission errors (verify
allowed-toolscoverage) - Argument passing works correctly with various input types
- The command completes the intended workflow without side effects
When updating a bundled command, increment the plugin version in plugin.json as specified in plugins/plugin-dev/skills/plugin-structure/SKILL.md#plugin-manifest-pluginjson. Consider adding a custom version field to the command frontmatter for internal tracking.
Implementation Examples
Minimal Project Command
For simple commands requiring no special permissions, create a basic Markdown file without frontmatter:
# Review Security
Review this code for security vulnerabilities, including:
- SQL injection
- XSS
- Authentication bypass
Save this as .claude/commands/review-security.md. It appears immediately as /review-security in the command picker.
Production Command with Full Frontmatter
A complete example from plugins/commit-commands/commands/commit.md demonstrates proper security scoping:
---
description: Run a git commit for staged changes
allowed-tools: Bash(git:add*,git:commit*,git:status*)
argument-hint: <commit-message>
disable-model-invocation: true
---
# Git Commit Helper
Create a concise commit message using the provided argument.
`$ARGUMENTS` will be used as the commit message.
1. Stage all changes: `git add .`
2. Commit with message: `git commit -m "$ARGUMENTS"`
3. Show the new commit hash.
Plugin-Bundled Distribution Command
For commands distributed as part of a plugin package:
---
description: Deploy the current project to staging
allowed-tools: Bash(./scripts/deploy.sh)
---
# Deploy to Staging
Run the project's deployment script.
./scripts/deploy.sh staging
Store this in my-deployer-plugin/commands/deploy-staging.md. When users install your plugin via Claude's plugin manager, this command becomes available in their workspace.
Safe Bash Invocation Pattern
Restrict Bash access to specific command families using the colon-separated syntax:
---
description: Show git status for the current repo
allowed-tools: Bash(git:status)
---
# Git Status
Run:
git status
This pattern prevents the command from executing arbitrary shell commands while permitting specific git operations.
Summary
- Location matters: Place commands in
.claude/commands/,~/.claude/commands/, or<plugin>/commands/based on sharing scope - Name correctly: Use kebab-case file names that become the command trigger (e.g.,
my-command.md→/my-command) - Frontmatter is mandatory for production: Include
description(under 60 characters) andallowed-toolswhitelist for security - Restrict Bash access: Use
Bash(command:subcommand*)patterns instead of openBashpermissions - Handle arguments safely: Use
$ARGUMENTSfor input, never interpolate directly into shell strings - Test locally: Verify in
.claude/commands/before distribution, checking/helpvisibility and execution flow - Document usage: Include a "Usage" section in the command body explaining arguments and side effects
Frequently Asked Questions
Where should I store slash commands for a team project?
Store project-specific commands in the .claude/commands/ directory at your repository root. Commit these files to version control so the entire team shares the same command definitions. According to plugins/plugin-dev/skills/plugin-structure/SKILL.md, Claude automatically discovers all .md files in this directory when loading the project context.
How do I prevent a slash command from running dangerous operations automatically?
Add disable-model-invocation: true to the command's YAML frontmatter. This flag forces manual execution and prevents Claude from invoking the command during autonomous workflows. Additionally, restrict the allowed-tools field to specific safe patterns like Bash(git:status) rather than open Bash access.
What is the difference between $ARGUMENTS and $ARGUMENT_1?
$ARGUMENTS captures the entire raw argument string provided after the command name. Use $ARGUMENT_1, $ARGUMENT_2, etc., to capture specific positional arguments when users provide space-separated inputs. The argument-hint frontmatter field should document which format your command expects.
Can I specify which AI model runs my slash command?
Yes. Add the model field to your frontmatter (e.g., model: haiku) to force a specific model for that command. Omit this field to inherit the model from the current conversation context. According to the command development guidelines, only specify a model when the command requires specific performance characteristics like faster response times or lower costs.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →