---
source_url: https://www.pubnub.com/docs/data-storage/message-history/retrieve-message-history
title: Retrieve message history
updated_at: 2026-09-30T07:20:08.000Z
---

# Retrieve message history

## Documentation index

To discover more PubNub resources:

1. Fetch [PubNub's llms.txt](https://www.pubnub.com/llms-full.txt) for a list of available pages in Markdown format.
2. Identify relevant URLs from that index.
3. Fetch the target pages.

Do not assume a path exists, always check the index first.

This guide shows you how to retrieve stored messages from PubNub channels using [Message Persistence](https://www.pubnub.com/docs/data-storage/message-history/overview.md). 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](https://admin.pubnub.com). If `read` access is restricted by [Access Manager](https://www.pubnub.com/docs/security/access-control/overview.md), your token must grant `read` on the channels you're querying.

For how Message Persistence works, see [Message Persistence](https://www.pubnub.com/docs/data-storage/message-history/overview.md).

## 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

```javascript
pubnub.fetchMessages(
  {
    channels: ["chats_guilds.mages_guild"],
    end: '15343325004275466',
    count: 100
  },
  function(status, response) {
    console.log(status, response);
  }
);
```

### Swift

```swift
pubnub.fetchMessageHistory(
  for: ["chats_guilds.mages_guild"],
  end: "15343325004275466"
) { result in
  switch result {
    case let .success(response):
      print("Successful History Fetch Response: \(response)")
    case let .failure(error):
      print("Failed History Fetch Response: \(error.localizedDescription)")
  }
}
```

### Objective-C

```objectivec
self.pubnub.history()
  .channels(@[@"chats_guilds.mages_guild"])
  .end(15343325004275466).limit(100)
  .performWithCompletion(^(PNHistoryResult *result, PNErrorStatus *status) {
    // handle returned messages in result
});
```

### Java

```java
pubNub.fetchMessages()
    .channels(Arrays.asList("chats_guilds.mages_guild"))
    .async(result -> {
        result.onSuccess(res -> {
            final Map<String, List<PNFetchMessageItem>> channelToMessageItemsMap = res.getChannels();
            final Set<String> channels = channelToMessageItemsMap.keySet();
            for (final String channel : channels) {
                List<PNFetchMessageItem> pnFetchMessageItems = channelToMessageItemsMap.get(channel);
                for (final PNFetchMessageItem fetchMessageItem : pnFetchMessageItems) {
                    System.out.println(fetchMessageItem.getMessage());
                    System.out.println(fetchMessageItem.getMeta());
                    System.out.println(fetchMessageItem.getTimetoken());
                }
            }
        }).onFailure(exception -> {
            exception.printStackTrace();
        });
    });
```

### C#

```csharp
pubnub.FetchHistory()
  .Channels(new string[] { "chats_guilds.mages_guild" })
  .MaximumPerChannel(100)
  .End(15343325004275466)
  .Execute(new PNFetchHistoryResultExt((result, status) => {
    // handle returned messages in result
  }));
```

### Python

```python
envelope = pubnub.fetch_messages()\
  .channels(["chats_guilds.mages_guild"])\
  .count(100)\
  .end(15343325004275466)\
  .sync()
```

### Kotlin

```kotlin
pubnub.fetchMessages(
    channels = listOf("chats_guilds.mages_guild"),
    page = PNBoundedPage(limit = 100)
).async { result, status ->
    if (!status.error) {
        result!!.channels.forEach { (channel, messages) ->
            println("Channel: $channel")
            messages.forEach { messageItem: PNFetchMessageItem ->
                println(messageItem.message)
                println(messageItem.timetoken)
            }
        }
    } else {
        status.exception?.printStackTrace()
    }
}
```

### Dart

```dart
var 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](https://www.pubnub.com/docs/sdks.md).

## Page through long history

To retrieve more messages than [one call returns](#fetch-missed-messages), 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.

:::note Unlimited retention keysets
On a keyset with Unlimited retention, `messageCounts` considers only messages published in the last 30 days.
:::

### JavaScript

```javascript
pubnub.messageCounts({
    channels: ["chats.room1", "chats.room2"],
    channelTimetokens: ['15518041524300251']
    }).then((response) => {
    console.log(response)
    }).catch((error) => {
    // handle error
  }
);
```

### Swift

```swift
pubnub.messageCounts(
  channels: ["chats.room1", "chats.room2"],
  timetoken: 15495750401727535
) { result in
  switch result {
    case let .success(response):
      print("Successful Message Count Response: \(response)")
    case let .failure(error):
      print("Failed Message Count Response: \(error.localizedDescription)")
  }
}
```

### Objective-C

```objectivec
self.client.messageCounts().channels(@[@"chats.room1", @"chats.room2"])
  .timetokens(@[@(15495750401727535)])
  .performWithCompletion(^(PNMessageCountResult *result, PNErrorStatus *status) {
    if (!status.isError) {
      // Client state retrieved number of messages for channels.
    }
    else {
      // handler error condition
    }
  });
```

### Java

```java
pubnub.messageCounts()
    .channels(Arrays.asList("chats.room1", "chats.room2"))
    .channelsTimetoken(Arrays.asList(15495750401727535L))
    .async(result -> {
        result.onSuccess(res -> {
            for (Map.Entry<String, Long> entry : res.getChannels().entrySet()) {
                entry.getKey(); // the channel name
                entry.getValue(); // number of messages for that channel
            }
        }).onFailure(exception -> {
            exception.printStackTrace();
        });
    });
```

### C#

```csharp
pubnub.MessageCounts()
  .Channels(new string[] { "chats.room1", "chats.room2" })
  .ChannelsTimetoken(new long[] { 15495750401727535 })
  .Execute(new PNMessageCountResultExt((result, status) => {
    if (status != null && status.Error)
    {
      Console.WriteLine(status.ErrorData.Information);
    }
    else
    {
      Console.WriteLine(pubnub.JsonPluggableLibrary.SerializeToJsonString(result));
    }
  }));
```

### Python

```python
envelope = pubnub.message_counts() \
    .channel(["chats.room1", "chats.room2"]) \
    .channel_timetokens([15495750401727535]) \
    .sync()

print(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](https://www.pubnub.com/docs/message-processing/serverless/overview.md) to index messages in your own database at publish time.

Last updated at: 2026-09-30T07:20:08.000Z
