Skip to content

Week 4: Custom Slash Commands

By now, you've been using OpenCode for a few weeks. You've seen how /init scaffolds agents and how the plan and build modes work. This week, we're going to teach you how to author your own slash commands — personalized shortcuts that automate the prompts you find yourself typing over and over.

Think of a custom command as a templated prompt with a name. Instead of retyping "Run the full test suite, show me failures, and suggest fixes," you type /test. The command lives in your project's opencode.jsonc config file (or in ~/.config/opencode/commands/ for global reuse).

Why Commands Matter

The Rule of Three: If you've typed the same prompt three times, you've earned the right to write a command.

Commands aren't just convenience — they're a form of judgment capture. When you write a command, you're encoding a best practice. You're saying: "Here's how I want to approach this task, every time." Later, when a teammate or future-you runs /test, they get the same rigor, the same coverage, the same fix strategy — without thinking.

Three Types of Prompts Worth Commanding

  1. Frequently repeated workflows — "run tests + show failures" happens every PR.
  2. High-stakes decisions — "security review this code" should always ask the same questions.
  3. Domain-specific boilerplate — "scaffold a React component" always needs the same setup.

Anatomy of a Command

Here's the simplest possible command:

{
  "command": {
    "test": {
      "template": "Run the full test suite. Show me any failures. Suggest fixes for each.",
      "description": "Run tests and suggest fixes"
    }
  }
}

When you type /test in the OpenCode TUI, it sends that template to the agent. The agent reads it, runs the test, and responds.

The $ARGUMENTS Placeholder

Commands that take input use $ARGUMENTS:

{
  "command": {
    "component": {
      "template": "Create a new React component named $ARGUMENTS with TypeScript support, Tailwind CSS styling, and test file.",
      "description": "Create a React component scaffold"
    }
  }
}

Now /component Button becomes: "Create a new React component named Button with TypeScript support, Tailwind CSS styling, and test file."

Optional Fields: Agent & Model

You can specify which agent and model to use:

{
  "command": {
    "security-check": {
      "template": "Audit this codebase for security vulnerabilities. Focus on: secrets in code, SQL injection, XSS, CORS misconfigs, and missing auth checks.",
      "description": "Security audit",
      "agent": "code-reviewer",    // Use a custom agent
      "model": "anthropic/claude-opus-4-1-20250805"  // Use a more powerful model
    }
  }
}

This command uses a code-reviewer subagent (read-only, safe) and the more capable Opus model for deep analysis.

Where Commands Live

Commands are defined in two places:

Project-Local Commands

In your project's .opencode/opencode.jsonc:

{
  "command": {
    "explain-file": {
      "template": "Explain what the file $ARGUMENTS does, line by line. Assume the reader is a junior developer.",
      "description": "Explain a file"
    }
  }
}

These are shared with your team and checked into git.

Global Commands

In your home directory's ~/.config/opencode/commands/:

~/.config/opencode/commands/
  ├── changelog.jsonc
  ├── write-tests.jsonc
  └── format-docs.jsonc

Global commands are personal — they travel with you across projects. Useful for cross-cutting workflows (documentation, changelog generation).

Demo: Building Two Real Commands

Command 1: /test

Let's say you run the test suite at least 3 times per sprint, and every time you have to: 1. Run npm test 2. Look at the output 3. Find the failures 4. Ask the agent to explain and fix them

Problem: repetitive, error-prone.

Solution:

{
  "command": {
    "test": {
      "template": "Run `npm test`. Show me the full output. If tests fail, explain why each one failed and propose a fix. If tests pass, celebrate and move on.",
      "description": "Run full test suite with fixes",
      "agent": "build"
    }
  }
}

Usage:

user> /test
agent> [runs npm test]
agent> [shows failures]
agent> [proposes fixes]
agent> [asks permission to apply]

Command 2: /component <name>

Every time you add a React component, you want: - TypeScript - Tailwind CSS - Unit test file - Exported from index.ts

Problem: without a template, the agent might skip the test or use CSS-in-JS instead of Tailwind.

Solution:

{
  "command": {
    "component": {
      "template": "Create a new React component named $ARGUMENTS in src/components/$ARGUMENTS.tsx. Use TypeScript (strict). Style it with Tailwind CSS classes. Include a companion test file src/components/$ARGUMENTS.test.tsx using vitest. Export the component from src/components/index.ts.",
      "description": "Create a React component scaffold",
      "agent": "build"
    }
  }
}

Usage:

user> /component LoginForm
agent> [creates src/components/LoginForm.tsx]
agent> [creates src/components/LoginForm.test.tsx]
agent> [updates src/components/index.ts]
agent> [asks permission to create]

Advanced: Shell Integration & File References

Commands can include dynamic data:

{
  "command": {
    "fix-failing-tests": {
      "template": "Here are the failing tests from the last run:\n\n!npm test 2>&1\n\nFix each failure, starting with the easiest. Explain your approach before you fix.",
      "description": "Fix failing tests"
    }
  }
}

The !command syntax runs npm test and injects the output into the prompt. This is powerful: the agent always has fresh test output.

You can also include file references:

{
  "command": {
    "review-auth": {
      "template": "Security-review this authentication module. Look for missing CSRF tokens, insecure password handling, and unencrypted secrets.\n\nAuth code:\n@src/auth.ts",
      "description": "Review auth module"
    }
  }
}

Common Mistakes

1. Too Vague

Bad: "template": "Make this code better"

Good: "template": "Refactor this code for readability. Use descriptive names, extract helper functions, and add comments for complex logic."

2. Too Specific (Not Reusable)

Bad: "template": "Fix the bug in the login form where password validation isn't working"

Good: "template": "Debug the failing test in $ARGUMENTS. Show the error, explain the root cause, and fix it."

3. Asking for Permission After Every Action

Commands that ask for permission at every step are frustrating. Instead, ask once at the end:

Bad: "template": "Should I add a test? [asks]. Should I run it? [asks]. Should I commit? [asks]."

Good: "template": "Add a comprehensive test for $ARGUMENTS. Run it to verify it passes. Show me the results and I'll decide what to do."

Judgment: When NOT to Write a Command

Don't write a command if:

  1. It's a one-time task. You're writing a login system once. You don't need /login-system. Just ask the agent directly.

  2. It's better as a skill or agent. If the task is complex (multi-step, requires deep judgment), it might be a skill (Week 5) or custom agent (Week 6).

  3. It's too broad. /code is useless. /write-rest-api is clearer, but still vague. /write-rest-api-with-auth is much better.

  4. The args become unmanageable. If your command needs 5+ positional arguments, you're overcomplicating it. Split it or use a custom agent instead.

Recap

  • Commands are templated prompts that live in opencode.jsonc or ~/.config/opencode/commands/.
  • Use them when: you've typed the same prompt 3+ times.
  • Structure: template (required), description, agent, model, $ARGUMENTS for input.
  • Common patterns: test running, scaffolding, security audits, code review.
  • Judge wisely: not every task earns a command. One-off work, complex reasoning, or highly parameterized workflows belong elsewhere.

Next week, you'll learn about skills — commands' cousin, which the agent discovers and uses without you invoking them. This week is about you driving the workflow with custom commands.


Further Reading