---
title: "defineBotCommand"
description: "Define a typed slash command — name, optional Standard Schema options, and a handler receiving CommandContext."
---

## Overview

`defineBotCommand` defines a slash command with full type inference: `ctx.options` is inferred from the `options` schema. Commands are registered via `createBot({ commands })` or `bot.onCommand`, and routed to the matching handler when the platform delivers an invocation.

## Signature

```ts
import { defineBotCommand } from "@copilotkit/bot";

function defineBotCommand<Schema extends ObjectSchema>(
  command: BotCommand<Schema>,
): BotCommand<Schema>;
```

## Parameters

<PropertyReference name="command" type="BotCommand<Schema>" required>
  The command definition.

  <PropertyReference name="name" type="string" required>
    Command name without the leading slash (e.g. `"triage"`). Matched case-insensitively.
  </PropertyReference>

  <PropertyReference name="description" type="string">
    Help text; also the registration label on surfaces that register commands up front (e.g. a Discord-style application-command API).
  </PropertyReference>

  <PropertyReference name="options" type="ObjectSchema">
    Optional Standard Schema for typed options. Used natively on surfaces that deliver structured args; on text-only surfaces (Slack) it's unused — read `ctx.text`.
  </PropertyReference>

  <PropertyReference name="handler" type="(ctx: CommandContext) => void | Promise<void>" required>
    The command handler.
  </PropertyReference>
</PropertyReference>

### CommandContext

<PropertyReference name="thread" type="Thread" required>
  The conversation the command was invoked in.
</PropertyReference>

<PropertyReference name="command" type="string" required>
  The invoked command name, normalized (no leading slash, lower-cased).
</PropertyReference>

<PropertyReference name="text" type="string" required>
  Raw argument string after the command name — the form Slack delivers.
</PropertyReference>

<PropertyReference name="options" type="TOptions" required>
  Parsed, typed options. Populated on surfaces with native structured args; empty on text-only surfaces.
</PropertyReference>

<PropertyReference name="user" type="PlatformUser">
  The invoking user, when the surface provides it.
</PropertyReference>

<PropertyReference name="platform" type="string" required>
  The active surface, e.g. `"slack"`.
</PropertyReference>

## Usage

```tsx
import { defineBotCommand } from "@copilotkit/bot";
import { z } from "zod";

const triage = defineBotCommand({
  name: "triage",
  description: "Summarize and file the current thread.",
  options: z.object({ priority: z.enum(["low", "high"]).optional() }),
  async handler({ thread, text }) {
    await thread.runAgent({ prompt: `Triage: ${text}` });
  },
});
```

A slash command's text is never posted to the channel, so it isn't in the history the adapter reconstructs — pass it to the agent explicitly with [`runAgent({ prompt })`](/reference/bot/classes/Thread).

## Behavior

- **Declare commands with the platform** — platforms only deliver commands they know about. On Slack, every command must **also** be declared in the app configuration (the manifest's `slash_commands` section, or "Slash Commands" in the app settings); an undeclared command is silently dropped, even over Socket Mode. See the [Slack quickstart](/slack).
- **Matching is case-insensitive** and ignores the leading slash; commands delivered by the platform but not registered on the bot are ignored.
- **Up-front registration** — on `start()`, declared commands are forwarded to adapters that implement `registerCommands` (e.g. a Discord-style application-command API); adapters without it, including Slack, are skipped.

## Related

- [createBot](/reference/bot/functions/createBot) — registering commands (`commands` option / `onCommand`)
- [Thread](/reference/bot/classes/Thread) — `runAgent({ prompt })`
- [defineBotTool](/reference/bot/functions/defineBotTool) — the tool analog
