System message
A system message is a message of type system. It reports an event in the channel rather than something a user wrote, for example a member joining or a moderation command. MessageList and VirtualizedMessageList render it with the MessageSystem override instead of the message UI. This recipe replaces it.
Best practices
- Keep system messages visually distinct from user messages, but quieter.
- Show the message's text: it says what happened.
- Show who triggered the event only where your app's privacy rules allow it. The sender is
message.user. - Replace
MessageSystemfor a different layout. For a different look only, stylestr-chat__message--system. - Test with every event your app turns into a system message, such as members being added and removed, and moderation commands.
Default system message component
The default is EventComponent. It renders the message's text in a centered line, or its html when MessageList's unsafeHTML prop is set:

A system message comes from the server. Your backend sends one with type: "system", as System messages shows, and adding or removing members can create one, as Channel members shows.
Custom system message component
This component shows the text, the time and the user who triggered the event:
To see it, mute a user with the /mute command followed by an @mention. The command has to be enabled for the channel type in the Dashboard.
import type { PropsWithChildren } from "react";
import { convertTimestampToDate } from "stream-chat";
import type { EventComponentProps } from "stream-chat-react";
import { Avatar, WithComponents } from "stream-chat-react";
import "./CustomSystemMessage.css";
const CustomSystemMessage = ({ message }: EventComponentProps) => {
const { created_at, text, user } = message;
const date = convertTimestampToDate(created_at);
return (
<div className="custom-system-message">
<div>
Event: <strong>{text?.trim()}</strong> at {date?.toString()}
</div>
{user && (
<div className="custom-system-message__actor">
Actor:
<Avatar
imageUrl={user.image}
size="sm"
userName={user.name || user.id}
/>
{user.name}
</div>
)}
</div>
);
};
// Declared once, so WithComponents gets the same object on every render.
const overrides = { MessageSystem: CustomSystemMessage };
export const WithCustomSystemMessages = ({ children }: PropsWithChildren) => (
<WithComponents overrides={overrides}>{children}</WithComponents>
);.custom-system-message {
display: flex;
flex-direction: column;
align-items: center;
padding: 1rem;
border-radius: var(--str-chat__radius-md);
background: linear-gradient(
to right,
orange,
yellow,
green,
cyan,
blue,
violet
);
}
.custom-system-message__actor {
display: flex;
align-items: center;
gap: 0.5rem;
}Wrap the channel in it, so every message list inside renders system messages with CustomSystemMessage:
import type { Channel as StreamChannel } from "stream-chat";
import { Channel, MessageComposer, MessageList } from "stream-chat-react";
import { WithCustomSystemMessages } from "./CustomSystemMessage";
export const ChannelWithSystemMessages = ({
channel,
}: {
channel: StreamChannel;
}) => (
<WithCustomSystemMessages>
<Channel channel={channel}>
<MessageList />
<MessageComposer />
</Channel>
</WithCustomSystemMessages>
);
How it works:
- The list renders
MessageSystemfor every message of typesystem, and passes the message asmessage. Unlike a message UI, it renders outsideMessageContext, so the message comes from its props. created_atis a nanosecond number, soconvertTimestampToDateturns it into a date; Data conventions explains why.message.useris the user the server sent the message as. A system message your backend sends without a user has none, so the actor line is left out.
MessageList also passes unsafeHTML. To render a system message's html the way the default does, read that prop as well.
The props are on ComponentContext, under MessageSystem. The other parts of the message list, such as the date separators, are in Message list UI.