# Commands

A command is a message that starts with `/` and a command's name, such as `/giphy cats` or `/ban @ann spam`. The server runs the command when the message arrives. Each channel type has its own list of commands, which the client reads from `channel.config.availableCommands`.

>
> **Info:** **Example**
>
> The user wants to send a GIF of cats:
>
> 1. The user types `/` and picks Giphy from the suggestions.
> 2. The composer keeps Giphy as the active command, apart from the text, and sets aside anything the user had typed. The UI can show the command as a chip.
> 3. The user types "cats" and sends. The message goes out as `/giphy cats`, and the server answers with a GIF.
>

## The active command

Command suggestions open when the user types `/` at the start of the text. They list the channel type's commands from `channel.config.availableCommands`, matched locally, without a request: the names that contain what was typed, those that start with it first. [Suggestions](https://getstream.io/chat/docs/sdk/react-native/state/writing-messages/suggestions/) explains how suggestions work in general.

The active command is in `textComposer.command`, or `null` when there is none.

Picking a command sets `textComposer.command`. The composer then saves its content and empties itself, keeping only the command, so the user types the command's arguments on their own. Typing a whole command and then a space, as in `/giphy cats`, sets it too, and moves the command's name out of the text, so the text keeps `cats`. A typed command leaves the rest of the composer's content as it is. `TextComposer.clearCommand()` drops the command and puts the saved attachments, poll and other content back. When `TextComposer.clearCommand()` drops the command, a command picked from the suggestions comes back with an empty text. A command set with `TextComposer.setCommand()` brings back the text the composer had when it was called.

Saving and restoring the content are the built-in `command.activate` and `command.clear` effects, which [Effect handlers](https://getstream.io/chat/docs/sdk/react-native/state/writing-messages/composer-middleware/#effect-handlers) explains.

In the SDK's composer, the commands button of the attachment picker calls `TextComposer.setCommand()` for the command the user picks, unless the command is disabled. The button shows while `hasCommands` is `true`, which by default holds when the channel type has commands. The composer shows the active command as a chip beside the text input, which clears it when pressed, and the input's placeholder asks for the command's argument: a search for Giphy, and a username for the moderation commands.

## The command middleware

The active command is kept apart from the text, and goes back in front of it when the message is sent. Four middleware do that, and `stream-chat` installs them in every composer:

| Factory                                           | Middleware id                                                          | What it does                                                                                                  |
| ------------------------------------------------- | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| `createActiveCommandGuardMiddleware()`            | `stream-io/text-composer/active-command-guard`                         | While a command is active, stops the text chain before command suggestions, so no other command is suggested. |
| `createCommandStringExtractionMiddleware()`       | `stream-io/text-composer/command-string-extraction`                    | Takes a typed command's name out of the text: `/ban @ann spam` keeps `@ann spam`.                             |
| `createCommandInjectionMiddleware(composer)`      | `stream-io/message-composer-middleware/command-string-injection`       | Puts the command back in front of the text when the message is sent: `cats` goes out as `/giphy cats`.        |
| `createDraftCommandInjectionMiddleware(composer)` | `stream-io/message-composer-middleware/draft-command-string-injection` | Does the same when a draft is saved.                                                                          |

The guard sits after the mentions middleware, so mentions keep working while a command is active, which commands such as `/ban @ann` need. The two injection middleware leave text that already starts with the command as it is, so the command never goes out twice. [Built-in chains](https://getstream.io/chat/docs/sdk/react-native/state/writing-messages/composer-middleware/#built-in-chains) shows where each one runs.

### A UI without a command chip

The SDK's composer shows the active command as a chip beside the text. A composer of your own that shows the command in the text instead removes all four middleware, so the text keeps `/giphy cats` and is sent as typed. Remove them in the composer setup function:

```ts
import { StreamChat } from "stream-chat";

const client = StreamChat.getInstance("YOUR_API_KEY");

client.config.setSetupFunction("messageComposer", ({ composer }) => {
  composer.textComposer.middlewareExecutor.remove([
    "stream-io/text-composer/active-command-guard",
    "stream-io/text-composer/command-string-extraction",
  ]);
  composer.compositionMiddlewareExecutor.remove(
    "stream-io/message-composer-middleware/command-string-injection",
  );
  composer.draftCompositionMiddlewareExecutor.remove(
    "stream-io/message-composer-middleware/draft-command-string-injection",
  );
});
```

Remove all four, not only the two in the text chain. With the injection middleware left in, the command stays active after the user deletes `/giphy` from the text, and `cats` still goes out as `/giphy cats`.

Without the injection middleware, a command set with `TextComposer.setCommand()`, for example from the attachment picker's commands button, isn't added to the text. Such a UI writes the command's name and a space, such as `/giphy` followed by a space, into the text itself. The [commands UI cookbook](https://getstream.io/chat/docs/sdk/react-native/ui-cookbooks/composer/commands-ui/) builds a composer around the command.

## Sending a command

Some commands can't be used in some messages:

- While a message is being edited, no command can be used.
- While the message quotes another message, moderation commands can't be used.

`MessageComposer.isCommandDisabled(command)` tells whether a command is disabled. A disabled command can't be picked or sent. The composer [publishes a notification](https://getstream.io/chat/docs/sdk/react-native/state/publishing-notifications/) instead, with severity `warning` and type `validation:command:disabled`.

Each command in `channel.config.availableCommands` has an `args` field: the help text for its arguments, which the command suggestions show. The built-in commands all take arguments, so the message needs text after the command's name, such as `cats` after `/giphy`.

A command your server defines with empty `args` takes no arguments, and the command alone is the message. For example, a `/shrug` command that your server answers with a shrug. While such a command is active, `textComposer.activeCommandIsContent` is `true`, so the composer counts the command as content, and the user can send it with no text. [Custom commands](https://getstream.io/chat/docs/node/custom-commands-webhook/) shows how to define a command and its `args` on the server.

Some commands also need more than their name before they can be sent. `/ban`, `/mute`, `/unmute` and `/unban` need a mentioned user, and `/ban` needs a reason too. Sending such a command too early publishes a notification with severity `warning` and type `validation:command:not-ready`, and `MessageComposer.send()` returns `'nothing-to-send'`. `composer.isCommandSendable` tells whether the active command is ready.

To change those rules, register a `sendValidator`. Return `{ command, ready: false, reason }` to hold a command back, and the default validator's answer for the rest:

```ts
import { defaultCommandSendabilityValidator, StreamChat } from "stream-chat";
import type { CommandSendValidator } from "stream-chat";

const client = StreamChat.getInstance("YOUR_API_KEY");

const sendValidator: CommandSendValidator = (context) => {
  const { command, commandArgsText } = context;

  if (command.name === "giphy" && !commandArgsText) {
    return { command, ready: false, reason: "missing-query" };
  }

  return defaultCommandSendabilityValidator(context);
};

client.config.set({ messageComposer: { commands: { sendValidator } } });
```

## Replacing command search

`CommandSearchSource` loads the command suggestions, and also recognizes a command typed in full. To change which commands are offered, replace the commands middleware with one built on a search source of your own. Pass the composer too: without it, a disabled command isn't refused when it's picked or typed.

```ts
import {
  CommandSearchSource,
  createCommandsMiddleware,
  StreamChat,
} from "stream-chat";
import type { TextComposerMiddleware } from "stream-chat";

const client = StreamChat.getInstance("YOUR_API_KEY");

const HIDDEN_COMMANDS = ["ban", "unban"];

class WithoutBanSearchSource extends CommandSearchSource {
  // `query()` serves both the suggestions and a command typed in full.
  query(searchQuery: string) {
    const { items, next } = super.query(searchQuery);
    return {
      items: items.filter(({ name }) => !HIDDEN_COMMANDS.includes(name ?? "")),
      next,
    };
  }
}

client.config.setSetupFunction("messageComposer", ({ composer }) => {
  const { channel, textComposer } = composer;

  // Same id as the built-in commands middleware, so it takes its place.
  textComposer.middlewareExecutor.replace([
    createCommandsMiddleware(channel, {
      composer,
      searchSource: new WithoutBanSearchSource(channel),
    }) as TextComposerMiddleware,
  ]);

  return () =>
    textComposer.middlewareExecutor.replace([
      createCommandsMiddleware(channel, { composer }) as TextComposerMiddleware,
    ]);
});
```

A hidden command typed in full goes out as plain text, and the server still runs it. To turn a command off for good, remove it from the channel type. `hasCommands={false}` on `Channel` and `Thread` hides the attachment picker's commands button only.

## Pitfalls

- **A typed command leaves the text.** In a composer without a command chip, `/giphy` disappears as soon as the user types it. Remove the command middleware, as [A UI without a command chip](#a-ui-without-a-command-chip) shows.
- **A command can't be added on the client.** `channel.config.availableCommands` is the channel type's list as the server sends it.

## Used in

- [Commands and Giphy](https://getstream.io/chat/docs/sdk/react-native/features/rich-content/commands-and-giphy/)

---

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react-native/state/writing-messages/commands/](https://getstream.io/chat/docs/sdk/react-native/state/writing-messages/commands/).