In-Meeting Chat
Metered ships a native text and file chat that lives inside the meeting. Messages travel over the meeting's own connection, so there is no separate chat service to provision, authenticate, or keep in sync with the call.
Chat is enabled per room with the enableChat room setting. It works in the prebuilt iframe UI and,
now, directly from the Video SDK, so you can build your own chat surface with the methods and events
below.
Turning chat on
Set enableChat to true on the room, either when you create it or later through the
Sending and receiving text
Call meeting.sendChatMessage(text) to send a message to everyone in the meeting. Each participant
receives it through the chatMessageReceived event.
meeting.sendChatMessage("Hello everyone 👋");
meeting.on("chatMessageReceived", (message) => {
// message.type is "text" | "file" | "image"
console.log(`${message.senderName}: ${message.content}`);
});
A chatMessageReceived payload carries:
{
_id: "…", // stable message id (use it for de-duping / downloads)
senderParticipantSessionId: "…",
senderName: "…",
recipientParticipantSessionId: null, // set only for directed (private) messages
type: "text", // "text" | "file" | "image"
content: "Hello everyone 👋", // text body (raw — always escape before rendering)
fileName: null, // set on file / image messages
fileMimeType: null,
fileSizeBytes: null,
created: 1700000000000, // sort key, paired with _id
downloadToken: null // set on file / image messages
}
Text is delivered raw (trimmed, up to 5000 characters). Always escape it before you put it in the DOM. React does this for you.
See the SDK reference for
`sendChatMessage`and the`chatMessageReceived`event.Directed (private) messages
To send a message to one participant instead of the whole meeting, call
`meeting.sendChatMessageTo(participantSessionId, text)`. The participant session id is the `_id` you get from the`participantJoined`event (or from your own `join()` result).meeting.on("participantJoined", (participant) => {
// participant._id is the participantSessionId you can direct a message to
meeting.sendChatMessageTo(participant._id, "Welcome — this note is just for you.");
});
Both the sender and the recipient receive the message through chatMessageReceived, with
recipientParticipantSessionId set to the recipient. No other participant ever sees a directed
message.
Typing indicator
Call
`meeting.sendTypingIndicator()`while the local user is typing. Other participants receive a`chatTyping`event. The server throttles typing broadcasts to about one every 2 seconds, so it is safe to call on every keystroke.inputEl.addEventListener("input", () => meeting.sendTypingIndicator());
meeting.on("chatTyping", ({ participantSessionId, senderName }) => {
showTypingHint(`${senderName} is typing…`);
});
Loading history
Call
`meeting.getChatHistory(before, limit, beforeId)`to fetch stored messages. Results arrive on the`chatHistory`event, oldest-first. To load the first page, call it with no arguments.// First page
meeting.getChatHistory();
meeting.on("chatHistory", ({ chatRoomId, before, messages }) => {
render(messages);
// Page backwards: use the OLDEST message in this batch as the cursor.
const oldest = messages[0];
if (oldest) {
meeting.getChatHistory(oldest.created, 50, oldest._id);
}
});
History pages backwards by (created, _id): pass the oldest message's created as before and its
_id as beforeId. When a page comes back empty, you have reached the start of the stored history.
Chat history retention
How long messages are stored is controlled per room by the chatHistoryRetentionHours setting
(a value from 0 to 24), which you set through the
The default is 0: messages are delivered but never stored. So that late joiners still see the
conversation, the SDK keeps an in-session buffer and serves messages sent after they joined —
nothing from before their arrival, and nothing survives the session.
Set it to any whole number of hours from 1 to 24 (the dashboard offers 1, 6, 12 and 24) to store messages and have them auto-deleted after that many
hours. A few things to keep in mind:
- Lowering the value is not retroactive. Messages already written keep the expiry they were stored with; only messages written after the change use the shorter window.
- File attachments are always removed after 24 hours, regardless of the retention setting.
File sharing
Sharing a file is a two-step flow: upload the bytes to get a file reference, then send a chat message that points at it.
- Upload the file with
POST /api/v1/chat/upload, authenticated with the chat access token from`meeting.getChatAccessToken()`in the `Authorization: Bearer` header. The response gives you four fields describing the stored file. - Send a file message with`meeting.sendChatFileMessage(...)`, passing those four fields.
async function shareFile(file) {
const res = await fetch("https://<appname>.metered.live/api/v1/chat/upload", {
method: "POST",
headers: { Authorization: `Bearer ${meeting.getChatAccessToken()}` },
body: (() => {
const form = new FormData();
form.append("file", file);
return form;
})(),
});
const { fileS3Key, fileName, fileMimeType, fileSizeBytes } = await res.json();
meeting.sendChatFileMessage(fileS3Key, fileName, fileMimeType, fileSizeBytes);
}
Recipients receive the file message as a normal chatMessageReceived event whose type is file
(or image) and which carries a downloadToken scoped to that one message. Download the attachment
with GET /api/v1/chat/file/<messageId>?dl=<downloadToken>:
meeting.on("chatMessageReceived", (message) => {
if (message.type === "file" || message.type === "image") {
const url =
`https://<appname>.metered.live/api/v1/chat/file/${message._id}` +
`?dl=${message.downloadToken}`;
// e.g. render <a href={url}>{message.fileName}</a>
}
});
For the full request/response details, constraints, and the header-token download option for SDK callers, see
Chat File Sharing.Errors and limits
Operations that are rejected surface on the
`chatMessageError`event rather than throwing:meeting.on("chatMessageError", ({ context, message }) => {
console.warn(`Chat ${context} failed: ${message}`);
});
Per-participant rate limits:
| Operation | Limit |
|---|---|
| Sending messages | 20 per 10 seconds |
| Loading history | 10 per 10 seconds |
| File uploads | 10 per minute |
| Typing indicator | 1 broadcast per 2 seconds |
File constraints:
- 10 MB maximum per file.
- Uploads must match the MIME allowlist (images, PDF, plain text/CSV, ZIP, and common Office documents). The full list is on theChat File Sharingpage.
Chat methods throw while the socket is disconnected, and until the meeting has finished reconnecting. Guard against this by only sending once the meeting is connected.