# Message storage and history The history feature enables users to retrieve messages that have been previously sent in a room. [Message persistence](https://ably.com/docs/chat/rooms#persistence) is enabled by default for all chat rooms, with messages stored for 30 days. You can extend this retention period up to 365 days by [contacting us](https://ably.com/support). ## Retrieve previously sent messages Use the [`messages.history()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Messages.html#history)[`messages.history()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/messages/history(withparams:))[`messages.history()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-messages/history.html) method to retrieve messages that have been previously sent to a room. This returns a paginated response, which can be queried further to retrieve the next set of messages. Use the [`history()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-react.UseMessagesResponse.html#history) method available from the response of the `useMessages` hook to retrieve messages that have been previously sent to a room. This returns a paginated response, which can be queried further to retrieve the next set of messages. ```javascript const historicalMessages = await room.messages.history({ orderBy: OrderBy.NewestFirst, limit: 50 }); console.log(historicalMessages.items); if (historicalMessages.hasNext()) { const next = await historicalMessages.next(); console.log(next); } else { console.log('End of messages'); } ``` ```react const MyComponent = () => { const { history } = useMessages(); const handleGetMessages = () => { // fetch the last 3 messages, oldest to newest history({ limit: 3, orderBy: OrderBy.OldestFirst }) .then((result) => console.log('Previous messages: ', result.items)); }; return (
); }; ``` ```swift let paginatedResult = try await room.messages.history(withOptions: .init(orderBy: .newestFirst)) print(paginatedResult.items) if let next = try await paginatedResult.next { print(next.items) } else { print("End of messages") } ``` ```kotlin var historicalMessages = room.messages.history(orderBy = OrderBy.NewestFirst) println(historicalMessages.items.toString()) // historical messages are paginated, so we can iterate through while (historicalMessages.hasNext()) { historicalMessages = historicalMessages.next() println(historicalMessages.items.toString()) } println("End of messages") ``` ```jetpack var historicalMessages = room.messages.history(orderBy = OrderBy.NewestFirst) println(historicalMessages.items.toString()) // historical messages are paginated, so we can iterate through while (historicalMessages.hasNext()) { historicalMessages = historicalMessages.next() println(historicalMessages.items.toString()) } println("End of messages") ```
The following optional parameters can be passed when retrieving previously sent messages: | Parameter | Description | | --------- | ----------- | | start | Earliest time to retrieve messages from, as a unix timestamp in milliseconds. Messages with a timestamp equal to, or greater than, this value will be returned. | | end | Latest time to retrieve messages from, as a unix timestamp in milliseconds. Messages with a timestamp less than this value will be returned. | | orderBy | The order in which to retrieve messages from; either `oldestFirst` or `newestFirst`. | | limit | Maximum number of messages to be retrieved per page, up to 1,000. | ## Retrieve messages sent prior to subscribing Users can also retrieve historical messages that were sent to a room before the point that they registered a listener by [subscribing](https://ably.com/docs/chat/rooms/messages#subscribe). The order of messages returned is from most recent, to oldest. This is useful for providing conversational context when a user first joins a room, or when they subsequently rejoin it later on. It also ensures that the message history they see is continuous, without any overlap of messages being returned between their subscription and their history call. Use the [`historyBeforeSubscribe()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.MessageSubscriptionResponse.html#historyBeforeSubscribe)[`historyBeforeSubscribe(withParams:)`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/messagesubscriptionresponse/historybeforesubscribe%28withparams%3A%29))[`historyBeforeSubscribe()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-messages-subscription/history-before-subscribe.html) function returned as part of a [message subscription](https://ably.com/docs/chat/rooms/messages#subscribe) response to only retrieve messages that were received before the listener was subscribed to the room. This returns a paginated response, which can be queried further to retrieve the next set of messages. Use the [`historyBeforeSubscribe()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-messages-subscription/history-before-subscribe.html) function returned as part of a [message subscription](https://ably.com/docs/chat/rooms/messages#subscribe) response to only retrieve messages that were received before the listener was subscribed to the room. This returns a paginated response, which can be queried further to retrieve the next set of messages. Use the [`historyBeforeSubscribe()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-react.UseMessagesResponse.html#historyBeforeSubscribe) method available from the response of the `useMessages` hook to only retrieve messages that were received before the listener subscribed to the room. As long as a defined value is provided for the listener, and there are no message discontinuities, `historyBeforeSubscribe()` will return messages from the same point across component renders. If the listener becomes undefined, the subscription to messages will be removed. If you subsequently redefine the listener then `historyBeforeSubscribe()` will return messages from the new point of subscription. This returns a paginated response, which can be queried further to retrieve the next set of messages. ```javascript const { historyBeforeSubscribe } = room.messages.subscribe(() => { console.log('New message received'); }); const historicalMessages = await historyBeforeSubscribe({ limit: 50 }); console.log(historicalMessages.items); if (historicalMessages.hasNext()) { const next = await historicalMessages.next(); console.log(next); } else { console.log('End of messages'); } ``` ```react const MyComponent = () => { const [loading, setLoading] = useState(true); const { historyBeforeSubscribe } = useMessages({ listener: (message) => { console.log('Received message: ', message); }, onDiscontinuity: (error) => { console.log('Discontinuity detected:', error); setLoading(true); }, }); useEffect(() => { // once the listener is subscribed, `historyBeforeSubscribe` will become available if (historyBeforeSubscribe && loading) { historyBeforeSubscribe({ limit: 10 }).then((result) => { console.log('Previous messages: ', result.items); setLoading(false); }); } }, [historyBeforeSubscribe, loading]); return
...
; }; ``` ```swift let messagesSubscription = try await room.messages.subscribe() let paginatedResult = try await messagesSubscription.historyBeforeSubscribe(withParams: .init(limit: 50)) print(paginatedResult.items) if let next = try await paginatedResult.next { print(next.items) } else { print("End of messages") } ``` ```kotlin val subscription = room.messages.subscribe { println("New message received") } var historicalMessages = subscription.historyBeforeSubscribe(limit = 50) println(historicalMessages.items.toString()) while (historicalMessages.hasNext()) { historicalMessages = historicalMessages.next() println(historicalMessages.items.toString()) } println("End of messages") ``` ```jetpack import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.* import com.ably.chat.MessagesSubscription import com.ably.chat.Room @Composable fun HistoryBeforeSubscribeComponent(room: Room) { var messages by remember { mutableStateOf>(emptyList()) } var subscription by remember { mutableStateOf(null) } DisposableEffect(room) { subscription = room.messages.subscribe { println("New message received") } onDispose { subscription?.unsubscribe() } } LaunchedEffect(subscription) { subscription?.let { sub -> var historicalMessages = sub.historyBeforeSubscribe(limit = 50) println(historicalMessages.items.toString()) messages = historicalMessages.items.map { it.text } while (historicalMessages.hasNext()) { historicalMessages = historicalMessages.next() println(historicalMessages.items.toString()) messages = messages + historicalMessages.items.map { it.text } } println("End of messages") } } // Display messages in UI Column { messages.forEach { message -> Text(message) } } } ```
The following parameters can be passed when retrieving previously sent messages: | Parameter | Description | | --------- | ----------- | | start | Earliest time to retrieve messages from, as a unix timestamp in milliseconds. Messages with a timestamp equal to, or greater than, this value will be returned. | | end | Latest time to retrieve messages from, as a unix timestamp in milliseconds. Messages with a timestamp less than this value will be returned. | | limit | Maximum number of messages to be retrieved per page, up to 1,000. | ## Messages pagination Alternatively, you can use the [`collectAsPagingMessagesState()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat-extensions-compose/com.ably.chat.extensions.compose/collect-as-paging-messages-state.html) composable function to observe messages with automatic pagination support. This function provides a complete solution for displaying chat messages in Jetpack Compose by: - Subscribing to new messages in real-time and adding them to the list - Handling message updates and deletions automatically - Listening to [message reaction](https://ably.com/docs/chat/rooms/message-reactions) events and updating messages with reaction summaries - Managing pagination when scrolling near the end of the list using `historyBeforeSubscribe()` - Handling [discontinuities](https://ably.com/docs/chat/rooms/messages#discontinuity) by clearing and reloading messages - Only fetching messages when the room is in the attached state - Cleaning up resources when the composable leaves the composition The function accepts two optional parameters: - `scrollThreshold`: The number of items remaining before reaching the last item at which fetching the next page is triggered (default: 10) - `fetchSize`: The number of messages to load per page (default: 100) The returned [`PagingMessagesState`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat-extensions-compose/com.ably.chat.extensions.compose/-paging-messages-state/index.html) provides: - `loaded`: A list of messages in reverse chronological order (newest first) - `listState`: A `LazyListState` for controlling the scroll position and triggering automatic pagination - `loading`: Whether a loading operation is in progress - `hasMore`: Whether there are more messages to load (returns `false` before the first page is fetched) - `error`: Any error encountered during loading (of type `ErrorInfo`) - `refresh()`: A function to clear the error state and retry loading ```jetpack import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.Button import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.Text import androidx.compose.runtime.Composable import com.ably.chat.Room import com.ably.chat.annotations.ExperimentalChatApi import com.ably.chat.extensions.compose.collectAsPagingMessagesState @OptIn(ExperimentalChatApi::class) @Composable fun MessagesComponent(room: Room) { val pagingMessagesState = room.collectAsPagingMessagesState( scrollThreshold = 10, fetchSize = 50, ) // Handle error state pagingMessagesState.error?.let { error -> Text("Error: ${error.message}") Button(onClick = { pagingMessagesState.refresh() }) { Text("Retry") } } LazyColumn( reverseLayout = true, state = pagingMessagesState.listState, ) { items( items = pagingMessagesState.loaded, key = { it.serial }, ) { message -> Text("${message.clientId}: ${message.text}") } // Show loading indicator when fetching messages if (pagingMessagesState.loading) { item { CircularProgressIndicator() } } } } ```