---
source_url: https://www.pubnub.com/docs/general/storage
title: Message Persistence
updated_at: 2026-05-21T15:45:39.323Z
---

> Documentation Index
> For a curated overview of PubNub documentation, see: https://www.pubnub.com/docs/llms.txt
> For the full list of all documentation pages, see: https://www.pubnub.com/docs/llms-full.txt


# Message Persistence

Message Persistence reliably stores messages as they are published. You can easily retrieve messages that arrive while a device is offline. Set retention for specific periods or unlimited time.

:::note Network interruptions
PubNub automatically reconnects after interruptions for a smooth experience. See the [Connection Management guide](https://www.pubnub.com/docs/general/setup/connection-management#handle-network-interruptions) for retry and backoff details.
:::

When a message is published, PubNub stores it with the channel name and the message’s publish timetoken. You can use this data to retrieve, delete, or annotate messages.

You can retrieve the following:

* [Messages](https://www.pubnub.com/docs/general/storage#retrieve-messages)
* [Message reactions](https://www.pubnub.com/docs/general/messages/actions#retrieve-actions)
* [File Sharing](https://www.pubnub.com/docs/general/files#retrieve-files)

###### Extend stored messages with identity, actions, and types

Each stored message records the publishing client's [User ID](https://www.pubnub.com/docs/general/setup/users-and-devices), allowing you to attribute history to individual users or filter client-side by publisher. Persisted messages also support [message actions](https://www.pubnub.com/docs/general/messages/actions) that let you attach reactions, receipts, or custom annotations after publish. You can retrieve messages by their [internal type](https://www.pubnub.com/docs/general/messages/type#server-response) to distinguish regular messages (type `0`) from file messages (type `4`). Use the `custom_message_type` field and timetokens to filter and organize your stored message history.

###### Understand how timetokens order your message timeline

Every message stored in Message Persistence carries the timetoken assigned at [publish](https://www.pubnub.com/docs/general/messages/publish#send-messages) time. Because PubNub assigns timetokens server-side as a globally monotonic 17-digit nanosecond value, the stored message sequence on each channel is consistent regardless of which client published. Use `start` and `end` timetokens to paginate through history, resume after a disconnect using the last received timetoken, or scope [Illuminate queries](https://www.pubnub.com/docs/admin-api/illuminate-introduction) to a precise time window with `PUBLISH_TIMETOKEN`.

## Message storage options

You can publish and subscribe in real-time without storing any messages in PubNub, but there are significant benefits to using Message Persistence. Choose the approach that fits your architecture:

| Approach | When to use it |
| --- | --- |
| Store in PubNub | You want built-in history, message counts, and actions without managing your own storage infrastructure. |
| Store externally | You have an existing data store, need custom querying, or run analytics pipelines that require data from other systems. For more information, refer to [Events & Actions](https://www.pubnub.com/docs/serverless/events-and-actions/overview). |
| Store in both | You want PubNub history for real-time retrieval and also forward data to an external system for analytics or long-term archiving. For more information, refer to [Events & Actions](https://www.pubnub.com/docs/serverless/events-and-actions/overview). |

:::tip Exporting messages to external systems
[Events & Actions](https://www.pubnub.com/docs/serverless/events-and-actions/overview) is the recommended no-code way to forward published messages to external targets, using webhooks, Amazon SQS, Amazon Kinesis, or S3. Configure it directly from the Admin Portal without writing any code.
:::

## Configuration

Enable Message Persistence for your app’s keyset in the [Admin Portal](https://admin.pubnub.com/). For retention options, see [Message retention](#message-retention).

:::tip Public Admin Portal demo
Want to browse through the Admin Portal without creating an account? Explore it through the [Public Demo](https://demo-admin.pubnub.com/) that shows examples of most PubNub features for transport and logistics use case.
:::

By default, Message Persistence is enabled on every newly created keyset. Testing keysets default to `7 days` retention. Retention is how long messages remain in storage before deletion. For retention options, see [Message retention](#message-retention).

![Message Persistence settings in Admin Portal: retention options and toggles](https://www.pubnub.com/assets/images/message-persistence-f983709d976ef2abcc235c51608bfcdd.png)

| Option | Description |
| --- | --- |
| **Retention** | How long messages are saved. Free accounts support 1 day or 7 days. Paid accounts support higher limits. Align message retention with your [File Sharing](https://www.pubnub.com/docs/general/files) settings to keep messages and files for the same duration. |
| **Enable Delete-From-History** | A setting that lets you use API calls to delete specific messages previously stored in a channel's message history. |
| **Include presence events** | Includes Presence events in saved history so you can track them later. |

## Message retention

To retain a message, make sure Message Persistence is enabled for the target keyset in the [Admin Portal](https://admin.pubnub.com). You may also configure the retention duration, enable Delete-From-History, and choose whether to retain Presence events. Once configured, Message Persistence is enabled for all channels in the keyset.

:::warning Immutable message retention
Retention settings apply to future messages only. When you update Message Persistence retention, existing messages keep their original retention period. New messages use the updated retention period.
:::

###### Secure message retrieval and deletion with Access Manager

When Access Manager is enabled, `read` permission on a channel is required to fetch messages and get message counts. `delete` permission is required to remove messages from history. These permissions apply per channel, so you can grant history access independently of publish or subscribe access. See the [Message Persistence permissions table](https://www.pubnub.com/docs/general/security/access-control#message-persistence) for the full mapping.

## Retrieve messages

Use the Message Persistence API to retrieve messages quickly. Get up to 100 for one channel, or 25 across up to 500 channels, in one request.

The Message Persistence API returns [regular messages](https://www.pubnub.com/docs/general/messages/publish#send-messages), [file messages](https://www.pubnub.com/docs/general/files#send-files), and [message actions](https://www.pubnub.com/docs/general/messages/actions). The `messageType` and `custom_message_type` values indicate the PubNub type (integer) and your custom type (string). Use these values to tag messages (text, signals, files) and later filter or group them.

Message Persistence enables retrieval of three data types stored in PubNub channels: messages, message actions, and file messages.

| Message Type | Description | Saved in Message Persistence |
| --- | --- | --- |
| 0 | Regular message | Yes |
| 1 | Signal | No |
| 2 | App Context event | No |
| 3 | Message Actions event | Yes |
| 4 | File message | Yes |

:::note Retrieving message actions
Retrieve messages with their actions, or retrieve only the actions. See [Retrieve Actions](https://www.pubnub.com/docs/general/messages/actions#retrieve-actions) for details.
:::

Use `start` and `end` timetokens to get a time range. For multi‑channel requests, 25 is the default and maximum.

To fetch missed messages, provide only `end` with the last received timetoken. The example below retrieves the last 25 messages on two channels.

###### JavaScript

```javascript
pubnub.fetchMessages(
  {
    channels: ["chats.room1", "chats.room2"],
    end: '15343325004275466',
    count: 25 // default/max is 25 messages for multiple channels (up to 500)
  },
  function(status, response) {
    console.log(status, response);
  }
);
```

###### Swift

```swift
pubnub.fetchMessageHistory(
  for: ["chats.room1", "chats.room2"],
  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.room1", @"chats.room2"])
  .end(15343325004275466).limit(25)
  .performWithCompletion(^(PNHistoryResult *result, PNErrorStatus *status) {
    // handle returned messages in result
});
```

###### Java

```java
pubNub.fetchMessages()
    .channels(Arrays.asList("ch1", "ch2", "ch3"))
    .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[] { "my_channel" })
  .MaximumPerChannel(25)
  .End(15343325004275466)
  .Execute(new PNFetchHistoryResultExt((result, status) => {
    // handle returned messages in result
  }));
```

###### Python

```python
envelope = pubnub.fetch_messages()\
  .channels(["chats.room1", "chats.room2"])\
  .count(25)\
  .end(15343325004275466)\
  .sync()
```

If you need more than 25 messages, use returned timetokens to page backward and retrieve more. Continue paging through the channel timeline until you have all required messages.

| Parameters Used | Behavior |
| --- | --- |
| `start` | Retrieves messages **before** the `start` timetoken, **excluding** any message at that timetoken |
| `end` | Retrieves messages **after** the `end` timetoken, **including** any message at that timetoken |
| `start` & `end` | Retrieves messages **between** the `start` and `end` timetokens, excluding any message at the `start` timetoken and including any message at the `end` timetoken |

Convert Unix timestamps to PubNub timetokens with the converter tool.

Unix Timestamp (seconds)

### Filtering retrieved messages

Message Persistence prioritizes low‑latency retrieval. To filter by content, retrieve messages and filter on the client. You can also filter by the `type` and `custom_message_type` values to show specific message types that you used previously (for example, `vip-chat` or `intruder-alert`). For server‑side search, use an After Publish Function to index messages in your database.

If you store messages in an external database, you can use an [After Publish Function](https://www.pubnub.com/docs/serverless/functions/overview#after-publish-or-fire-functions) to index messages server-side and query them with full flexibility. For a no-code approach to routing messages to external systems, see [Events & Actions](https://www.pubnub.com/docs/serverless/events-and-actions/overview).

For more information or assistance, [contact support](https://www.pubnub.com/docs/mailto:support@pubnub.com).

### Delete messages from history

Message Persistence lets you delete messages from history. You can delete messages published between two points in time or delete a specific message.

Deleting messages from history requires SDK clients initialized with the secret key. Each SDK provides different methods. Consult the [SDK documentation](https://www.pubnub.com/docs/sdks) for reference.

For details on initializing PubNub with a secret key, see [Application Configuration](https://www.pubnub.com/docs/general/setup/application-setup#initialize-with-secret-key).

## Receive messages and signals

When a message or signal is received by the client, it triggers a `message` or `signal` event. Refer to [Receive Messages](https://www.pubnub.com/docs/general/messages/receive) to learn how to act on these events.

## How messages are fetched

The following details explain how messages are retrieved from Message Persistence based on the start and end parameters.

### Scenario 1

**Scenario 1** retrieves messages starting with the message stored before the start timetoken parameter value and continues until it has 25 messages or hits that oldest message (whichever comes first).

| Parameter | Value |
| --- | --- |
| `count` | 25 |
| `start` | value provided |
| `end` | value not provided |

```text
Channel Timeline
oldest-message --------------- start-timetoken --------------- newest-message
[                   <--------]
```

### Scenario 2

| Parameter | Value |
| --- | --- |
| `count` | 25 |
| `start` | value not provided |
| `end` | value provided |

```text
Channel Timeline
oldest-message --------------- end-timetoken --------------- newest-message
                               [                    <---------------------]
```

### Scenario 3

| Parameter | Value |
| --- | --- |
| `count` | 25 |
| `start` | value provided |
| `end` | value provided |

```text
Channel Timeline
oldest-message ----- end-timetoken ----------------- start-timetoken ----- newest-message
                     [      <----------------------]
```

## Get message counts

The Message Count API returns the number of messages sent after a given point in time. You can specify up to 100 channels in a single call.

Rather than retrieving lots of messages from hundreds of channels, you can get the number of missed messages and retrieve the messages later. For example, you can display the unread message count on channels that the client hasn't visited yet and retrieve those messages when the client actually visits the channel.

The Message Count API returns the number of messages sent for each channel greater than or equal to the provided timetoken. You can not specify a time range because it's only intended to give the number of messages since a given timetoken. Optionally, you can specify a different timetoken per channel or one timetoken to be applied to all channels.

:::note Unlimited message retention
For keys with unlimited message retention enabled, this method 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)
```

## Terms in this document

* **Timetoken** - A unique identifier for each message that represents the number of 100-nanosecond intervals since January 1, 1970, for example, 16200000000000000.
