Commands and Giphy
Commands are messages that start with a slash, such as /giphy cats or /mute @bob, which the server runs instead of posting as text. This page covers how users pick a command in the composer, what has to be installed for a picked command to be sent, and the Giphy preview.
What users get
The user types / at the start of the input. A list shows the commands of the channel's type, each with its arguments, such as /giphy [text] and /ban [@username] [text]. The attachment menu has the same list under Commands. When they pick giphy, the composer shows a giphy chip before the input and empties itself. If they remove the chip with its ✕, the files and other content they had added come back; the text they typed to find the command doesn't. Had they picked it from the Commands menu, their text would come back too. While the chip shows, the attachment menu and the emoji button are hidden, and the send button shows a checkmark.
They type cats and send. A Giphy preview appears in the list, marked "Only visible to you", with three buttons: Send posts the GIF to the channel, Shuffle shows another one, and Cancel removes the preview.

Some commands can't be sent yet, such as /mute without a mentioned user, and some can't be used in every message, such as any command while a message is edited. The composer then shows a warning and sends nothing. Sending a command lists the rules.
How it works across the layers
- State. The channel type's commands are in
channel.config.availableCommands. The text composer's commands middleware suggests them after/and keeps the picked one intextComposer.command. The server runs a command when a message's text starts with it. - UI. The suggestion list, the Commands menu in
AttachmentSelector, andCommandChipin the composer. In the message list,Attachmentrenders the Giphy preview with its buttons. - Layout. Not involved.
Picking a command
- The user types
/giand picksgiphy, from the suggestions or the Commands menu. The composer keeps it intextComposer.command, saves what it held, and empties itself. - They type
cats. While a command is active, mentions still work, so/mute @bobcan be completed. - On send, the composer checks that the command is ready (see Sending a command) and sends the message. Removing the chip instead drops the command and brings back what was saved.
The active command in State › Commands explains how commands are matched, and what is saved and restored.
In step 3, the command goes back in front of the text, so the message is sent as /giphy cats. The command middlewares that stream-chat installs in every composer do that: The command middlewares in State › Commands explains them.
The Giphy preview
- The server answers
/giphy catswith an ephemeral message:type: "ephemeral",command: "giphy", and an attachment of typegiphywith three actions. Only the user receives it, and it takes the place of their sent message in the list. Attachmentrenders the GIF withGiphy, and the buttons withAttachmentActions. Send has the focus, and after Send or Cancel the focus goes back where it was, usually the composer.- Each button calls
Channel.sendAction(message.id, { [action.name]: action.value }), with the action's value:send,shuffleorcancel. - When the response carries a message, it replaces the preview: another GIF after Shuffle, the posted message after Send. After Cancel it carries none, and the preview is removed from the list.
Channel.sendAction() changes no local state itself; the message list's action handler applies the response. In VirtualizedMessageList with separateGiphyPreview, the preview renders below the list as GiphyPreviewMessage instead of among the messages.
Turning it on or off
Which commands exist
The server decides. Each channel type has a list of commands, set in the Stream Dashboard or through the server API. The client can't add to it: channel.config.availableCommands is the server's list as it is. With an empty list, no suggestions open and the attachment menu has no Commands entry.
Commands in the text instead of a chip
By default, the composer shows the active command as a chip beside the text, and sends it in front of the text:
| The user enters | The composer shows | It sends |
|---|---|---|
/giphy cats, typed in full |
cats, beside the chip |
/giphy cats |
giphy picked from the suggestions or the Commands menu, then cats |
cats, beside the chip |
/giphy cats |
A draft saved while a command is active keeps the command the same way. A composer of your own that shows the command in the text, with no chip, removes the four command middlewares, as A UI without a command chip in State › Commands shows.
Hide commands in the UI
To stop offering commands in the composer, remove the commands middleware in your composer setup function, and the Commands entry from the attachment menu:
import {
AttachmentSelector,
defaultAttachmentSelectorActionSet,
} from "stream-chat-react";
client.config.setSetupFunction("messageComposer", ({ composer }) => {
composer.textComposer.middlewareExecutor.remove(
"stream-io/text-composer/commands-middleware",
);
});
const actionSetWithoutCommands = defaultAttachmentSelectorActionSet.filter(
(action) => action.type !== "selectCommand",
);
export const AttachmentSelectorWithoutCommands = () => (
<AttachmentSelector attachmentSelectorActionSet={actionSetWithoutCommands} />
);Register AttachmentSelectorWithoutCommands as the AttachmentSelector override. The server still runs a command a user types in full, so to turn a command off for good, remove it from the channel type.
Customizing
When a command can be sent
messageComposer.commands.sendValidator decides whether the active command is ready, for example to require a search term after /giphy. Commands has the example.
The command chip
CommandChip receives the active command. A chip of your own clears it with TextComposer.setCommand(null), which calls TextComposer.clearCommand() and restores what the composer saved when the command was picked:
import type { CommandChipProps } from "stream-chat-react";
import {
MessageComposer,
useMessageComposerController,
WithComponents,
} from "stream-chat-react";
const SlashCommandChip = ({ command }: CommandChipProps) => {
const { textComposer } = useMessageComposerController();
if (!command) return null;
return (
<button
aria-label={`Exit /${command.name}`}
className="slash-command-chip"
onClick={() => textComposer.setCommand(null)}
type="button"
>
/{command.name} ✕
</button>
);
};
const overrides = { CommandChip: SlashCommandChip };
export const ComposerWithSlashChip = () => (
<WithComponents overrides={overrides}>
<MessageComposer />
</WithComponents>
);Command suggestions
AutocompleteSuggestionList and AutocompleteSuggestionItem replace the suggestion list and its items; the Custom suggestions recipe shows both. The descriptions and arguments of the built-in commands are translation keys, such as command.giphy.description and command.giphy.args. A custom command's own description and arguments are used as keys too. See Localization.
The Giphy preview
<Attachment/> takes giphyVersion, the rendition of the GIF to load and show (fixed_height by default), and attachmentActionsDefaultFocus, which action gets the focus when the preview appears. This setup shows the original GIF, keeps the focus in the composer, and shows the preview below a virtualized list:
import type { AttachmentProps } from "stream-chat-react";
import {
Attachment,
Channel,
MessageComposer,
VirtualizedMessageList,
WithComponents,
} from "stream-chat-react";
const noDefaultFocus = {};
const AttachmentWithOriginalGiphy = (props: AttachmentProps) => (
<Attachment
{...props}
attachmentActionsDefaultFocus={noDefaultFocus}
giphyVersion="original"
/>
);
const overrides = { Attachment: AttachmentWithOriginalGiphy };
export const ChannelWithGiphyAboveComposer = () => (
<WithComponents overrides={overrides}>
<Channel channel={channel}>
<VirtualizedMessageList separateGiphyPreview />
<MessageComposer />
</Channel>
</WithComponents>
);To change the separate preview itself, register a GiphyPreviewMessage, which receives the preview message with the props the list passes to every message. To change the buttons, pass AttachmentActions to Attachment; it receives the attachment's actions and the actionHandler that sends them.
Reference
- Attachments: the
Attachmentprops, includingGiphy,AttachmentActions,giphyVersionandattachmentActionsDefaultFocus. - Composer UI components:
CommandChipand the composer's state while a command is active. - Attachment Selector: the Commands entry of the attachment menu.
- VirtualizedMessageList:
separateGiphyPreview. - Composer middleware and Suggestions: the text middleware behind commands.