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.
Prerequisites
Section titled “Prerequisites”- A
ChatUserClientwith a workinggetAccessToken. See Get the user’s Chat token. - Conversations that your backend already created. See Create direct conversations.
conversations.readmessages.readUser scope: chat:access1. List conversations
Section titled “1. List conversations”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.
2. Page with next_cursor
Section titled “2. Page with next_cursor”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.
3. Read a thread
Section titled “3. Read a thread”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”.
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();}4. Show or hide controls
Section titled “4. Show or hide controls”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.
Verify
Section titled “Verify”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.
Troubleshooting
Section titled “Troubleshooting”| 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. |