# 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() }
}
}
}
```