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:

  1. The command appears in /help with the correct description
  2. The command executes without permission errors (verify allowed-tools coverage)
  3. Argument passing works correctly with various input types
  4. 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) and allowed-tools whitelist for security
  • Restrict Bash access: Use Bash(command:subcommand*) patterns instead of open Bash permissions
  • Handle arguments safely: Use $ARGUMENTS for input, never interpolate directly into shell strings
  • Test locally: Verify in .claude/commands/ before distribution, checking /help visibility 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →