Skip to content

List conversations and read messages

Your app lists the user’s conversations and reads their messages with ChatUserClient. This guide covers paging, unread state and permission-aware controls.

Show an inbox of conversations, open one, and load older messages on demand.

Chat permissions (all of): conversations.readmessages.readUser scope: chat:access

conversations.list returns the conversations the user is a member of, plus PUBLIC channels, most recently active first. Pass type to filter by DIRECT, GROUP or CHANNEL.

Each item has has_unread, my_role, last_activity_at and status. For DIRECT and GROUP it embeds a short members list. A channel has only member_count. has_unread is true when the newest message is from someone else and newer than the user’s read position.

Every list returns { items, next_cursor }. The default page is 50 items and the maximum is 100 items. Pass next_cursor as before to get the next, older page. next_cursor is null on the last page. Send a cursor you did not get from a list and you receive invalid_cursor.

messages.list returns messages newest first. Reverse a page to render it oldest first. A deleted message comes back as a tombstone: status is DELETED and body is empty. Render it as “Message deleted”.

inbox.ts
import type { ChatConversation, ChatMessage } from '@central-code-station/ccs-chat-user-client';
import { chat } from './fe-guide-context';
export async function loadInbox(): Promise<{ conversations: ChatConversation[]; canSend: boolean }> {
// What may this user do here? Use it to show or hide controls. Chat enforces it anyway.
const { capabilities } = await chat.capabilities();
const { items } = await chat.conversations.list({ limit: 30 });
return { conversations: items, canSend: capabilities.includes('messages.send') };
}
/** Follows `next_cursor` until the last page. Most UIs load one page at a time instead. */
export async function loadAllConversations(): Promise<ChatConversation[]> {
const all: ChatConversation[] = [];
let before: string | undefined;
do {
const page = await chat.conversations.list({ before });
all.push(...page.items);
before = page.next_cursor ?? undefined;
} while (before);
return all;
}
export async function openThread(conversationId: string): Promise<{ messages: ChatMessage[]; older: string | null }> {
const page = await chat.messages.list(conversationId, { limit: 50 }); // newest first
return { messages: [...page.items].reverse(), older: page.next_cursor };
}
export async function loadOlder(conversationId: string, older: string): Promise<ChatMessage[]> {
const page = await chat.messages.list(conversationId, { before: older, limit: 50 });
return [...page.items].reverse();
}

capabilities() returns the user’s roles and Chat permissions in your application. Use them to decide which buttons to draw, for example “New chat” only with conversations.create.

Open a conversation your backend created for you. The inbox lists it, messages.list returns its messages, and has_unread turns false after you call markRead. See Typing, read state and presence.

You see Cause Fix
Empty inbox Your backend has not created a conversation, or the user is not a member. Create it from your backend and add the user.
403 missing_capability The role lacks conversations.read or messages.read in this application. A tenant administrator grants it. See Grant permissions to roles.
404 not_found The conversation belongs to another context, or the user cannot read it. Check tenantId and applicationId.
400 invalid_limit limit is not an integer from 1 to 100. Send a valid integer.