Retrieve message history
This guide shows you how to retrieve stored messages from PubNub channels using Message Persistence. It covers fetching missed messages, scoping requests to a time range, paging through histories longer than a single call, and counting unread messages without fetching them.
The examples on this page are written for the current SDK releases: C# 9.0.0, Dart 8.0.3, Java 6.4.5, JavaScript 13.0.3, Kotlin 13.4.4, Objective-C 7.0.4, Python 10.7.2, and Swift 10.2.0. Each example assumes a PubNub client initialized for its SDK, unless the example sets one up itself.
Prerequisite: Message Persistence must be enabled on the keyset before you store or read history, and new keysets don't enable it by default. Turn it on in the Admin Portal. If read access is restricted by Access Manager, your token must grant read on the channels you're querying.
For how Message Persistence works, see Message Persistence.
Fetch missed messages
The most common use case is catching up on messages published while your client was offline. Pass the timetoken of the last message your client received as the end parameter. A single history call returns up to 100 messages for one channel, or up to 25 messages per channel across as many as 500 channels.
- JavaScript
- Swift
- Objective-C
- Java
- C#
- Python
- Kotlin
- Dart
1pubnub.fetchMessages(
2 {
3 channels: ["chats_guilds.mages_guild"],
4 end: '15343325004275466',
5 count: 100
6 },
7 function(status, response) {
8 console.log(status, response);
9 }
10);
1pubnub.fetchMessageHistory(
2 for: ["chats_guilds.mages_guild"],
3 end: "15343325004275466"
4) { result in
5 switch result {
6 case let .success(response):
7 print("Successful History Fetch Response: \(response)")
8 case let .failure(error):
9 print("Failed History Fetch Response: \(error.localizedDescription)")
10 }
11}
1self.pubnub.history()
2 .channels(@[@"chats_guilds.mages_guild"])
3 .end(15343325004275466).limit(100)
4 .performWithCompletion(^(PNHistoryResult *result, PNErrorStatus *status) {
5 // handle returned messages in result
6});
1pubNub.fetchMessages()
2 .channels(Arrays.asList("chats_guilds.mages_guild"))
3 .async(result -> {
4 result.onSuccess(res -> {
5 final Map<String, List<PNFetchMessageItem>> channelToMessageItemsMap = res.getChannels();
6 final Set<String> channels = channelToMessageItemsMap.keySet();
7 for (final String channel : channels) {
8 List<PNFetchMessageItem> pnFetchMessageItems = channelToMessageItemsMap.get(channel);
9 for (final PNFetchMessageItem fetchMessageItem : pnFetchMessageItems) {
10 System.out.println(fetchMessageItem.getMessage());
11 System.out.println(fetchMessageItem.getMeta());
12 System.out.println(fetchMessageItem.getTimetoken());
13 }
14 }
15 }).onFailure(exception -> {
show all 18 lines1pubnub.FetchHistory()
2 .Channels(new string[] { "chats_guilds.mages_guild" })
3 .MaximumPerChannel(100)
4 .End(15343325004275466)
5 .Execute(new PNFetchHistoryResultExt((result, status) => {
6 // handle returned messages in result
7 }));
1envelope = pubnub.fetch_messages()\
2 .channels(["chats_guilds.mages_guild"])\
3 .count(100)\
4 .end(15343325004275466)\
5 .sync()
1pubnub.fetchMessages(
2 channels = listOf("chats_guilds.mages_guild"),
3 page = PNBoundedPage(limit = 100)
4).async { result, status ->
5 if (!status.error) {
6 result!!.channels.forEach { (channel, messages) ->
7 println("Channel: $channel")
8 messages.forEach { messageItem: PNFetchMessageItem ->
9 println(messageItem.message)
10 println(messageItem.timetoken)
11 }
12 }
13 } else {
14 status.exception?.printStackTrace()
15 }
show all 16 lines1var result = await pubnub.batch.fetchMessages({'chats_guilds.mages_guild'}, count: 100);
To avoid re-fetching the last message your client already received, pass lastReceivedTimetoken + 1 as end. A PubNub timetoken exceeds the largest integer a JavaScript Number represents exactly, so increment or compare timetokens with an arbitrary-precision integer type such as BigInt rather than with plain numeric arithmetic.
Fetch a time range
To retrieve messages between two points in time, pass both start and end to fetchMessages. The start parameter sets the newer boundary (exclusive) and must be a higher timetoken than end. The end parameter sets the older boundary (inclusive).
| Parameter | Boundary | Inclusive? | Timetoken value |
|---|---|---|---|
start | Newer (more recent) | No | Higher |
end | Older (less recent) | Yes | Lower |
Results return in oldest-first order regardless of the parameters you pass. For the exact parameter names and method signatures in your SDK, see your SDK reference.
Page through long history
To retrieve more messages than one call returns, use the timetokens returned in the response to page backward. Pass the timetoken of the oldest message in the current page as the start value in the next call. Continue until the response returns fewer messages than the requested count, which indicates you have reached the oldest available message.
How far back you can page depends on your keyset's configured retention period.
Count unread messages
To show an unread badge without fetching message content, use messageCounts. Pass the timetoken of the last message your client received. PubNub returns the number of messages published on or after that timetoken for each channel.
A single messageCounts call covers up to 100 channels. You can pass one timetoken that applies to all channels, or a different timetoken per channel.
Unlimited retention keysets
On a keyset with Unlimited retention, messageCounts considers only messages published in the last 30 days.
- JavaScript
- Swift
- Objective-C
- Java
- C#
- Python
1pubnub.messageCounts({
2 channels: ["chats.room1", "chats.room2"],
3 channelTimetokens: ['15518041524300251']
4 }).then((response) => {
5 console.log(response)
6 }).catch((error) => {
7 // handle error
8 }
9);
1pubnub.messageCounts(
2 channels: ["chats.room1", "chats.room2"],
3 timetoken: 15495750401727535
4) { result in
5 switch result {
6 case let .success(response):
7 print("Successful Message Count Response: \(response)")
8 case let .failure(error):
9 print("Failed Message Count Response: \(error.localizedDescription)")
10 }
11}
1self.client.messageCounts().channels(@[@"chats.room1", @"chats.room2"])
2 .timetokens(@[@(15495750401727535)])
3 .performWithCompletion(^(PNMessageCountResult *result, PNErrorStatus *status) {
4 if (!status.isError) {
5 // Client state retrieved number of messages for channels.
6 }
7 else {
8 // handler error condition
9 }
10 });
1pubnub.messageCounts()
2 .channels(Arrays.asList("chats.room1", "chats.room2"))
3 .channelsTimetoken(Arrays.asList(15495750401727535L))
4 .async(result -> {
5 result.onSuccess(res -> {
6 for (Map.Entry<String, Long> entry : res.getChannels().entrySet()) {
7 entry.getKey(); // the channel name
8 entry.getValue(); // number of messages for that channel
9 }
10 }).onFailure(exception -> {
11 exception.printStackTrace();
12 });
13 });
1pubnub.MessageCounts()
2 .Channels(new string[] { "chats.room1", "chats.room2" })
3 .ChannelsTimetoken(new long[] { 15495750401727535 })
4 .Execute(new PNMessageCountResultExt((result, status) => {
5 if (status != null && status.Error)
6 {
7 Console.WriteLine(status.ErrorData.Information);
8 }
9 else
10 {
11 Console.WriteLine(pubnub.JsonPluggableLibrary.SerializeToJsonString(result));
12 }
13 }));
1envelope = pubnub.message_counts() \
2 .channel(["chats.room1", "chats.room2"]) \
3 .channel_timetokens([15495750401727535]) \
4 .sync()
5
6print(envelope.result.channels)
Filter by message type
Message Persistence does not support server-side content filtering. To show only certain message types, fetch the messages and filter client-side using the messageType (integer) and custom_message_type (string) fields in each response item.
messageType | Content |
|---|---|
0 | Regular message |
3 | Message Action event |
4 | File message |
For server-side filtering or full-text search, use an After Publish Function to index messages in your own database at publish time.