Message history is every message saved in a conversation. Live events only show messages that arrive while you're connected, so you load history to show what came before: when the chat screen opens, and when the user scrolls up ("Load earlier").
Where this fits: your app is connected to a conversation (see the
Chat quickstart). Load the latest page of history, then
keep it up to date with live message events from Messages.
A typical chat screen
- The user opens the chat. Load the newest page (say, 50 messages).
- Show them, and start listening for live
messageevents. - The user scrolls to the top. Load the next older page with the cursor you got in step 1.
- Repeat until
hasMoreisfalse.
Load a page
In your app:
const page = await chat.messages.list({ room: 'conv_9WcQ…', limit: 50 });
// { data: [...], nextCursor: '...', previousCursor: '...', hasMore: true }
const older = await chat.messages.list({ before: page.nextCursor });
const newer = await chat.messages.list({ after: page.previousCursor });const { messages, loadMore, loading, hasMore } = useMessages();
<button onClick={() => loadMore()} disabled={!hasMore || loading}>Load earlier</button><LivqenoChat> loads the first page (50 messages by default) when it
connects. loadMore() loads older ones.
const page = await livqeno.chat!.messages.list({ room: 'conv_9WcQ…', limit: 50 });final page = await chat.history(limit: 50);
final older = await chat.history(before: page.nextCursor);How it works:
- Messages come newest first. Reverse them if your UI shows the oldest at the top.
- A cursor is a bookmark into the history. Use
nextCursorto get older messages, andpreviousCursorfor newer ones. - Pass a cursor back exactly as you got it. Don't try to read or build one.
hasMore: falsemeans you've reached the start of the conversation.
Why there's no offset
There's no "skip 100 messages" option, on purpose:
- A large offset is slow.
- In a live chat, new messages shift the offset. If 3 messages arrive while you load page 2, you'd see 3 messages twice.
- A cursor points to an exact spot, so it stays correct no matter what arrives.
After a reconnect
You don't need history to fill gaps after a dropped connection. The SDK
catches up on its own and delivers missed messages as normal message
events, then fires recovered. See Chat Events. You only load
history yourself if you use the raw WebSocket protocol.
Filters
| Filter | What it does | Use it for |
|---|---|---|
threadRootId | Only messages in one thread. | A thread side panel. See Threads. |
senderId | Only messages from one user. | "Messages from this customer". |
includeDeleted | Include deleted messages. | Moderation review. |
From your backend
Your backend can read history too, for example to export a support transcript or review reports. It uses the API key instead of a chat token, so it can read any conversation in the environment:
import { Livqeno } from '@livqeno/server';
const livqeno = new Livqeno({
apiKey: process.env.LIVQENO_API_KEY,
baseUrl: process.env.LIVQENO_API_URL, // https://api.livqeno.com
});
const page = await livqeno.chat.listMessages('support-room-42', { limit: 50 });from livqeno import ListChatMessagesParams
page = livqeno.chat.list_messages("support-room-42", ListChatMessagesParams(limit=50))Cursors work the same way on the backend and in the app.
Common errors
| Problem | Cause | Fix |
|---|---|---|
| Messages appear in reverse order | History is newest first. | Reverse the page before rendering, or use React's useMessages(), which is oldest first. |
| The same message shows twice after "Load earlier" | You added a live message that was also in a history page. | Use the message id as the key, and skip ids you already have. |
ROOM_NOT_FOUND | The user isn't a member, or the token doesn't include this conversation. | See Members. |
Next steps
- Threads: load a single thread.
- Delivery & Read Receipts: show unread counts next to history.
- Messages: sending and live events.