# How Goose Slash Commands Work: A Complete Guide to Creating Custom Commands

> Discover how Goose slash commands work and easily create your own custom commands. Learn to define shortcuts for built-in functions or user-defined recipes in this comprehensive guide.

- Repository: [Block Open Source/goose](https://github.com/block/goose)
- Tags: how-to-guide
- Published: 2026-04-05

---

**Goose slash commands provide shortcuts that trigger either built‑in CLI functions or user‑defined recipes, with custom commands stored as `SlashCommandMapping` entries in the global config and resolved at runtime by loading YAML recipe files.**

Goose, the open‑source AI agent framework from Block, supports extensible slash commands that streamline repetitive workflows. While built‑in commands like `/exit` and `/prompt` handle core interactions, the platform also allows you to create **custom slash commands** that bind unique trigger strings to reusable recipe files. Understanding how these commands are stored, resolved, and executed enables you to extend Goose with domain‑specific automation.

## Architecture of Goose Slash Commands

Goose distinguishes between two command types: **built‑in commands** hard‑coded in the CLI parser, and **custom commands** that dynamically map strings to recipe files. Custom mappings persist in the global configuration under the key `slash_commands`.

### Storage and Data Model

The data model for custom commands lives in [`crates/goose/src/slash_commands.rs`](https://github.com/block/goose/blob/main/crates/goose/src/slash_commands.rs). The `SlashCommandMapping` struct defines the relationship between a command trigger and its target file:

```rust
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SlashCommandMapping {
    pub command: String,      // e.g., "my-task"
    pub recipe_path: String, // absolute or relative path to a .yaml recipe
}

```

The module provides two key functions for persistence:

- `list_commands()` reads the vector from the global `Config` (`Config::global().get_param`) and falls back to an empty vector on error.
- `save_slash_commands()` writes the updated vector back to the same config key.

### Runtime Resolution

When a user enters text beginning with `/`, the system normalizes the input by trimming the leading slash and converting it to lowercase. The `resolve_slash_command` function in [`crates/goose/src/slash_commands.rs`](https://github.com/block/goose/blob/main/crates/goose/src/slash_commands.rs) then handles the lookup:

```rust
pub fn resolve_slash_command(command: &str) -> Option<Recipe> {
    let recipe_path = get_recipe_for_command(command)?;
    if !recipe_path.exists() { return None; }

    let recipe_content = std::fs::read_to_string(&recipe_path).ok()?;
    let recipe = Recipe::from_content(&recipe_content).ok()?;
    Some(recipe)
}

```

If the mapped file exists and parses correctly, the function returns a `Recipe` object that the agent loop executes.

### CLI Input Handling

The CLI input loop in [`crates/goose-cli/src/session/input.rs`](https://github.com/block/goose/blob/main/crates/goose-cli/src/session/input.rs) first checks for a leading `/` character. If present, it calls `handle_slash_command`:

```rust
match handle_slash_command(&input) {
    Some(result) => Ok(result),
    None => Ok(InputResult::Message(input.trim().to_string())),
}

```

The `handle_slash_command` function contains a match statement for every built‑in command (e.g., `/exit`, `/prompt`, `/recipe`). Critically, if the command is not matched, the function returns `None`, causing the caller to treat the input as a normal message. This fallback mechanism allows custom slash commands to pass through to the agent, where `resolve_slash_command` processes them later.

## Creating Custom Slash Commands

Adding a custom command requires creating a recipe file and registering the mapping in the global configuration.

### Step 1: Create a Recipe File

A recipe is a standard Goose YAML file defining steps the agent should execute. Create a file such as [`./recipes/my_task.yaml`](https://github.com/block/goose/blob/main/./recipes/my_task.yaml):

```yaml
name: My Task
description: Runs a custom workflow.
steps:
  - name: Echo
    type: bash
    command: echo "Running my custom task"

```

Save this in your project directory or a shared recipes folder.

### Step 2: Register the Command

You can register the mapping through three interfaces: programmatically via Rust, through the HTTP API, or by manually editing the configuration.

#### Programmatic Registration (Rust)

Use the `set_recipe_slash_command` function to bind a path to a command string:

```rust
use std::path::PathBuf;
use goose::slash_commands;

fn register() -> anyhow::Result<()> {
    slash_commands::set_recipe_slash_command(
        PathBuf::from("recipes/my_task.yaml"),
        Some("/my-task".to_string()),
    )?;
    Ok(())
}

```

This function automatically removes any existing mapping for the given recipe path before adding the new pair, and persists the change to the global config.

#### HTTP API

The Goose server exposes a POST endpoint at `/recipes/slash-command` defined in [`crates/goose-server/src/routes/recipe.rs`](https://github.com/block/goose/blob/main/crates/goose-server/src/routes/recipe.rs). Send a JSON payload:

```bash
curl -X POST https://your-goose-instance/api/recipes/slash-command \
  -H "Content-Type: application/json" \
  -d '{"file_path":"recipes/my_task.yaml","slash_command":"/my-task"}'

```

On success, the server updates the global configuration.

#### Manual Configuration

Edit the Goose configuration file directly (typically `~/.goose/config.json`) and add the mapping to the `slash_commands` array:

```json
{
  "slash_commands": [
    { "command": "my-task", "recipe_path": "recipes/my_task.yaml" }
  ]
}

```

Note that the stored command name omits the leading slash, though users must include it when typing.

### Step 3: Execution Flow

When a user types `/my-task`, the following occurs:

1. `handle_slash_command` returns `None` because `my-task` is not a built‑in command.
2. The session treats the text as a message and passes it to the agent loop in [`crates/goose/src/agents/execute_commands.rs`](https://github.com/block/goose/blob/main/crates/goose/src/agents/execute_commands.rs).
3. The agent calls `slash_commands::resolve_slash_command("/my-task")`.
4. The recipe loads and executes, producing the defined output.

## Adding New Built‑in Slash Commands

If you need a command that executes hard‑coded logic rather than a recipe, modify the CLI parser directly:

1. Open [`crates/goose-cli/src/session/input.rs`](https://github.com/block/goose/blob/main/crates/goose-cli/src/session/input.rs).
2. Add a new match arm in `handle_slash_command`:

```rust
"/mybuiltin" => {
    // Perform custom logic here
    Some(InputResult::Message("Running builtin".to_string()))
}

```

3. Add a corresponding variant to the `InputResult` enum if you require a new return type.

After recompiling the CLI (`cargo build -p goose-cli`), the command is recognized immediately without requiring a recipe mapping.

## Key Source Files

- **[`crates/goose/src/slash_commands.rs`](https://github.com/block/goose/blob/main/crates/goose/src/slash_commands.rs)**: Defines `SlashCommandMapping`, `resolve_slash_command`, and persistence logic.
- **[`crates/goose-cli/src/session/input.rs`](https://github.com/block/goose/blob/main/crates/goose-cli/src/session/input.rs)**: Implements `handle_slash_command` for built‑in command parsing.
- **[`crates/goose/src/agents/execute_commands.rs`](https://github.com/block/goose/blob/main/crates/goose/src/agents/execute_commands.rs)**: Invokes the slash command resolver during agent execution.
- **[`crates/goose-server/src/routes/recipe.rs`](https://github.com/block/goose/blob/main/crates/goose-server/src/routes/recipe.rs)**: Provides the HTTP endpoint for registering slash commands.
- **[`crates/goose-server/src/routes/config_management.rs`](https://github.com/block/goose/blob/main/crates/goose-server/src/routes/config_management.rs)**: Retrieves registered slash commands for UI display.

## Summary

- **Built‑in commands** are static match arms in `handle_slash_command` within the CLI crate.
- **Custom commands** are dynamic mappings stored in the global config under the `slash_commands` key as `SlashCommandMapping` structs.
- Use `set_recipe_slash_command` programmatically, POST to `/recipes/slash-command` via the API, or edit `~/.goose/config.json` to register new commands.
- Custom commands fallback through the CLI input handler and resolve later in the agent loop by loading and executing the associated YAML recipe.
- For hard‑coded behavior, extend `handle_slash_command` directly and recompile the CLI.

## Frequently Asked Questions

### Where are custom goose slash commands stored?

Custom slash commands persist in the global Goose configuration file (typically `~/.goose/config.json`) under the `slash_commands` key. Each entry is a JSON object containing the command name and the absolute or relative path to the associated recipe file.

### What is the difference between built‑in and custom slash commands in Goose?

Built‑in commands such as `/exit` and `/prompt` are hard‑coded in the `handle_slash_command` function within [`crates/goose-cli/src/session/input.rs`](https://github.com/block/goose/blob/main/crates/goose-cli/src/session/input.rs) and execute immediate logic. Custom commands are user‑defined mappings that associate a slash string with a YAML recipe file; when triggered, Goose loads the recipe and executes its steps through the agent loop.

### How do I register a custom slash command via the Goose API?

Send a POST request to the `/recipes/slash-command` endpoint with a JSON body containing `file_path` and `slash_command` fields. For example: `curl -X POST -H "Content-Type: application/json" -d '{"file_path":"recipes/task.yaml","slash_command":"/task"}' https://goose/api/recipes/slash-command`.

### Can I create a slash command that runs custom code instead of a recipe?

Yes, but this requires adding a new built‑in command. You must modify the `handle_slash_command` function in [`crates/goose-cli/src/session/input.rs`](https://github.com/block/goose/blob/main/crates/goose-cli/src/session/input.rs) to include a new match arm that returns the appropriate `InputResult`. After modifying the source, recompile the CLI with `cargo build -p goose-cli` to make the command available.