---
source_url: https://www.pubnub.com/docs/sdks/javascript/api-reference/message-persistence
title: Message Persistence API for JavaScript SDK
updated_at: 2026-11-09T13:29:01.000Z
sdk_name: PubNub JavaScript SDK
sdk_version: 13.1.3
description: "Reference for the PubNub JavaScript SDK Message Persistence methods: fetchMessages(), deleteMessages(), and messageCounts(). Covers the deprecated history() method."
---

# Message Persistence API for JavaScript SDK

PubNub JavaScript SDK, use the latest version: 13.1.3

Install:

```bash
npm install pubnub@13.1.3
```

> For AI agents: documentation index at https://www.pubnub.com/llms-full.txt

The PubNub JavaScript SDK reads, deletes, and counts the messages that Message Persistence stores, with `fetchMessages()`, `deleteMessages()`, and `messageCounts()`. Message Persistence must be enabled on the keyset before you store or read history, and new keysets don't enable it by default.

Each method returns a promise that resolves with the response and rejects with a `PubNubError`. The error carries a `status` object with `category` and `statusCode`, and `status.errorData` holds the details that PubNub returned for a failed request. Each method also takes a callback as the second argument, and with a callback the method returns nothing and PubNub calls it with `(status, response)`. On success, `status.error` is `false` and `status.operation` names the operation, and on failure `status.error` is `true` and `response` is `null`. All examples use `async/await`, so wrap a call in `try...catch` to receive the error status.

The methods take channel ID strings, not [SDK channel entities](https://www.pubnub.com/docs/sdks/javascript/api-reference/subscribe.md#sdk-entities).

Message Persistence gives you real-time access to the history of messages published to PubNub. Each message is timestamped to the nearest 10 nanoseconds and stored across multiple availability zones in several geographic locations. You can encrypt stored messages with AES-256 so they are not readable on PubNub’s network. For details, see [Message Persistence](https://www.pubnub.com/docs/data-storage/message-history/overview.md).

You control how long messages are stored through your account’s retention policy. Options include: 1 day, 7 days, 30 days, 3 months, 6 months, 1 year, or Unlimited.

You can retrieve the following:

* Messages
* Message reactions
* Files (using the File Sharing API)

## Fetch history

This method requires that the Message Persistence add-on is [enabled](https://support.pubnub.com/hc/en-us/articles/360051974791-How-do-I-enable-add-on-features-for-my-keys-) for your key in the [Admin Portal](https://admin.pubnub.com/). Related: [Message Persistence](https://www.pubnub.com/docs/data-storage/message-history/overview.md).

`fetchMessages()` returns the messages that Message Persistence stores for one or more channels, using a PubNub client for JavaScript.

The `start` and `end` timetokens control which messages the call returns and in what order.

* If you specify only `start`, the call returns messages older than that timetoken, because `start` is the newer, exclusive boundary of the query.
* If you specify only `end`, the call returns messages with a timetoken equal to or newer than `end`, because `end` is the older, inclusive boundary of the query.

:::warning How to use the start and end parameters
PubNub retrieves messages by searching **backward through time** (newest to oldest). Because of this, the parameter names are the reverse of their intuitive meaning:
* **start** is the **newer** boundary (higher timetoken value) - search begins here, **exclusive**
* **end** is the **older** boundary (lower timetoken value) - search stops here, **inclusive**
When you provide both parameters, `start` must be a **higher** timetoken than `end`.
```mermaid
graph LR
    A(["9:00 AM<br/>(oldest)"]) -->|"time flows →"| B["end = 10:00 AM<br/>older boundary<br/>inclusive ✓"]
    B --> C["messages<br/>retrieved"]
    C --> D["start = 11:00 AM<br/>newer boundary<br/>exclusive ✗"]
    D -->|"time flows →"| E(["11:59 AM<br/>(newest)"])
    D -.->|"◄ API searches backward"| B
    style A fill:#f8f9fa,stroke:#6c757d,color:#495057
    style E fill:#f8f9fa,stroke:#6c757d,color:#495057
    style B fill:#d4edda,stroke:#28a745,color:#000
    style C fill:#cce5ff,stroke:#004085,color:#000
    style D fill:#fff3cd,stroke:#856404,color:#000
```
9:00 AM10:00 AM11:00 AM11:59 AM
Results are always returned in oldest-first order.
:::

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. If more messages match the time range, call again and set `start` to the timetoken of the oldest message in the previous response.

| Parameter | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| channels | string[] | Yes | n/a | IDs of the channels to fetch messages from, up to 500 channels. With `includeMessageActions` set to `true`, pass one channel only. With more than one channel, the call rejects with a `PubNubError` whose `status.message` says that history can return actions data for a single channel only. |
| start | string | Optional | n/a | Timetoken that sets the newer boundary of the query (exclusive). Retrieval begins at this timetoken and moves backward in time. With `end`, `start` must be a higher timetoken than `end`. |
| end | string | Optional | n/a | Timetoken that sets the older boundary of the query (inclusive). Retrieval stops at this timetoken. With `start`, `end` must be a lower timetoken than `start`. |
| count | number | Optional | n/a | Number of messages to return for each channel. The default is `100` for one channel, and `25` for several channels or with `includeMessageActions` set to `true`. The SDK lowers a larger value to that default. |
| includeMessageType | boolean | Optional | `true` | Whether each returned message includes its PubNub message type in the `messageType` property. |
| includeCustomMessageType | boolean | Optional | n/a | Whether each returned message includes the custom message type that its publisher set, in the `customMessageType` property. A message carries it only when the publisher set one. For the background, see [Message Persistence](https://www.pubnub.com/docs/data-storage/message-history/overview.md). |
| includeUUID | boolean | Optional | `true` | Whether each returned message includes the User ID of its publisher in the `uuid` property. |
| includeMeta | boolean | Optional | `false` | Whether each returned message includes the metadata that its publisher set at publish time, in the `meta` property. |
| includeMessageActions | boolean | Optional | `false` | Whether the response includes the message actions of each message, which limits the call to one channel and 25 messages. With more than one channel, the call rejects with a `PubNubError` whose `status.message` says that history can return actions data for a single channel only. |

### Sample code

Retrieve a message from a channel:

### Returns

`fetchMessages()` resolves with `channels`, an object that maps each channel ID to an array of messages. With a callback, `status.operation` is `PNFetchMessagesOperation`. Each message has these properties:

| Property | Type | Description |
| --- | --- | --- |
| channel | string | ID of the channel that holds the message. |
| timetoken | string | Timetoken that PubNub assigned to the message. |
| message | any | Decrypted message payload. |
| messageType | number | PubNub message type, `-1` for a regular message and `4` for a file message. Present with `includeMessageType`. |
| customMessageType | string | Custom message type that the publisher set. Present only when the publisher set one. |
| uuid | string | User ID of the publisher. Present with `includeUUID`. |
| meta | any | Metadata that the publisher set at publish time. Present only when the message has metadata. |
| actions | object | Message actions on the message, grouped by action type and then by action value. Present with `includeMessageActions` when the message has actions. |
| error | string | Decryption error. Present only when the SDK fails to decrypt the message, and `message` then holds the undecrypted payload. |

With `includeMessageActions` set to `true`, a response that PubNub truncates also has a `more` property. PubNub truncates when the returned messages hold more than 25000 message actions in total. To fetch the remaining messages, call `fetchMessages()` again with `more.start` as `start` and `more.max` as `count`.

The `more` property holds `url`, `start`, and `max`.

### Examples

#### Syntax

```typescript
pubnub.fetchMessages({
    channels: string[],
    start?: string,
    end?: string,
    count?: number,
    includeMessageType?: boolean,
    includeCustomMessageType?: boolean,
    includeUUID?: boolean,
    includeMeta?: boolean,
    includeMessageActions?: boolean
}): Promise<FetchMessagesResponse>;
```

#### Basic usage

```javascript
import PubNub from 'pubnub';
// Initialize PubNub with your keys
const pubnub = new PubNub({
  publishKey: 'YOUR_PUBLISH_KEY',
  subscribeKey: 'YOUR_SUBSCRIBE_KEY',
  userId: 'YOUR_USER_ID',
});
```

```javascript
// Function to fetch message history
const result = await pubnub.fetchMessages({
  channels: ['my-channel'],
  count: 1, // Number of messages to retrieve
  includeCustomMessageType: true, // if you want to include custom message type in the response
  start: 'replace-with-start-timetoken', // start timetoken
  end: 'replace-with-end-timetoken', // end timetoken
});
console.log('Fetched Messages:', result);
```

#### Basic usage (React)

In React, call `fetchMessages` inside a `useEffect` hook on component mount and store the results in state to display them in your UI.

```jsx
import React, { useState, useEffect } from 'react';
import PubNub from 'pubnub';

const pubnub = new PubNub({
  publishKey: 'YOUR_PUBLISH_KEY',
  subscribeKey: 'YOUR_SUBSCRIBE_KEY',
  userId: 'YOUR_USER_ID',
});

function MessageHistory() {
  const [messages, setMessages] = useState([]);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    async function loadHistory() {
      try {
        const result = await pubnub.fetchMessages({
          channels: ['my_channel'],
          count: 25,
        });
        const channelMessages = result.channels['my_channel'] ?? [];
        setMessages(channelMessages.map((m) => m.message));
      } catch (error) {
        console.error('fetchMessages failed:', error);
      } finally {
        setIsLoading(false);
      }
    }
    loadHistory();
  }, []);

  if (isLoading) return <p>Loading history...</p>;

  return (
    <ul>
      {messages.map((msg, i) => (
        <li key={i}>{typeof msg === 'object' ? JSON.stringify(msg) : msg}</li>
      ))}
    </ul>
  );
}
```

#### Returns

```json
{
  "channels": {
    "my-channel": [
      {
        "channel": "my-channel",
        "timetoken": "15483367794816642",
        "message": "message_1",
        "messageType": -1,
        "customMessageType": "text-message",
        "uuid": "my-uuid"
      }
    ]
  }
}
```

#### Fetch messages with metadata and actions

```javascript
const response = await pubnub.fetchMessages({
  channels: ['my_channel'],
  stringifiedTimeToken: true,
  includeMeta: true,
  includeMessageActions: true,
  includeCustomMessageType: true,
});
console.log('fetch messages response:', response);
```

#### Fetch messages with metadata and actions response

```json
{
  "channels": {
    "my_channel": [
      {
        "channel": "my_channel",
        "timetoken": "15741125155552287",
        "message": { "text": "Hello world!" },
        "messageType": -1,
        "customMessageType": "your custom type",
        "uuid": "someUserId",
        "meta": {},
        "actions": {
          "reaction": {
            "smiley_face": [
              {
                "uuid": "my-uuid",
                "actionTimetoken": "15741125155943280"
              }
            ]
          }
        }
      }
    ]
  },
  "more": {
    "url": "/v3/history-with-actions/sub-key/subscribeKey/channel/myChannel?start=15610547826970000&max=98",
    "start": "15610547826970000",
    "max": 98
  }
}
```

## Delete messages from history

This method requires that the Message Persistence add-on is [enabled](https://support.pubnub.com/hc/en-us/articles/360051974791-How-do-I-enable-add-on-features-for-my-keys-) for your key in the [Admin Portal](https://admin.pubnub.com/). Related: [Message Persistence](https://www.pubnub.com/docs/data-storage/message-history/overview.md).

`deleteMessages()` deletes stored messages from channel history, optionally within a time range, using a PubNub client for JavaScript.

Delete-From-History must be enabled for the keyset in the Admin Portal before a delete call succeeds. The JavaScript SDK also needs a `secretKey` in its configuration, and only the Node.js build signs requests with it. See [Server operations](https://www.pubnub.com/docs/sdks/javascript/api-reference/configuration.md#server-operations).

The `start` timetoken is always the exclusive boundary and the `end` timetoken is always the inclusive boundary. The call deletes the message at `end` but not the message at `start`, whichever of the two values is larger. For the full walkthrough, see [Delete messages from history](https://www.pubnub.com/docs/data-storage/message-history/delete-messages-from-history.md).

| Parameter | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| channel | string | Yes | n/a | ID of the channel to delete messages from. The method takes the ID string, not an [SDK channel entity](https://www.pubnub.com/docs/sdks/javascript/api-reference/subscribe.md#sdk-entities). |
| start | string | Optional | n/a | Timetoken that sets the exclusive boundary of the range to delete. The call keeps the message at this timetoken. |
| end | string | Optional | n/a | Timetoken that sets the inclusive boundary of the range to delete. The call deletes the message at this timetoken. |

### Returns

`deleteMessages()` resolves with an empty object, `{}`. With a callback, `status.operation` is `PNDeleteMessagesOperation`.

### Examples

#### Syntax

```typescript
pubnub.deleteMessages({
    channel: string,
    start?: string,
    end?: string
}): Promise<DeleteMessagesResponse>;
```

#### Basic usage

```javascript
const result = await pubnub.deleteMessages({
  channel: 'ch1',
  start: 'replace-with-start-timetoken',
  end: 'replace-with-end-timetoken',
});
console.log('Messages deleted successfully:', result);
```

#### Delete a single message

To delete one message, pass its publish timetoken, which a successful `publish()` returns, as `end`. Pass the next higher or the next lower timetoken as `start`. For example, if `15526611838554310` is the publish timetoken, pass `15526611838554310` as `end` and either `15526611838554309` or `15526611838554311` as `start`. Both pairs delete only the message at `15526611838554310`.

Timetokens exceed the largest integer that a JavaScript `Number` represents exactly, so compute the neighboring timetoken with `BigInt`.

```javascript
const response = await pubnub.deleteMessages({
  channel: 'ch1',
  start: 'replace-with-start-timetoken',
  end: 'replace-with-end-timetoken',
});
console.log('delete messages response:', response);
```

## Message counts

This method requires that the Message Persistence add-on is [enabled](https://support.pubnub.com/hc/en-us/articles/360051974791-How-do-I-enable-add-on-features-for-my-keys-) for your key in the [Admin Portal](https://admin.pubnub.com/). Related: [Message Persistence](https://www.pubnub.com/docs/data-storage/message-history/overview.md).

`messageCounts()` returns the number of messages published to one or more channels since a timetoken, using a PubNub client for JavaScript.

The count includes every message with a timetoken greater than or equal to the timetoken you pass for the channel. On a keyset with Unlimited retention, `messageCounts` considers only messages published in the last 30 days.

| Parameter | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| channels | string[] | Yes | n/a | IDs of the channels to count messages in. |
| channelTimetokens | string[] | Yes | n/a | Timetokens from which to count messages, listed in the same order as `channels`. A single timetoken applies to all channels. Any other list must hold one timetoken per channel. If the number of channels and timetokens differ and more than one timetoken is given, the call rejects with a `PubNubError` whose `status.message` says that the lengths do not match. |

### Returns

`messageCounts()` resolves with `channels`, an object that maps each channel ID to its message count. A channel without messages has a count of `0`. A channel with 10,000 messages or more has a count of `10000`. With a callback, `status.operation` is `PNMessageCountsOperation`.

### Examples

#### Syntax

```typescript
pubnub.messageCounts({
    channels: string[],
    channelTimetokens: string[]
}): Promise<MessageCountResponse>;
```

#### Basic usage

```javascript
const result = await pubnub.messageCounts({
  channels: ['chats.room1', 'chats.room2'],
  channelTimetokens: ['replace-with-timetoken'], // single timetoken applied to all channels
});
console.log('Message counts retrieved successfully:', result);
```

#### Returns

```json
{
  "channels": {
    "ch1": 49
  }
}
```

#### Count messages with a different timetoken for each channel

```javascript
const response = await pubnub.messageCounts({
  channels: ['ch1', 'ch2', 'ch3'],
  channelTimetokens: [
    'replace-with-channel-timetoken-ch1', // timetoken for channel ch1
    'replace-with-channel-timetoken-ch2', // timetoken for channel ch2
    'replace-with-channel-timetoken-ch3', // timetoken for channel ch3
  ],
});
console.log('message count response:', response);
```

## Deprecated history method

`history()` is a deprecated method of the PubNub JavaScript SDK that fetches the messages of one channel, and `fetchMessages()` replaces it.

The reference for apps that still call `history()` or read the deprecated `data` property of `actions` is [Message Persistence API: deprecated history() method and actions data property for JavaScript SDK](https://www.pubnub.com/docs/sdks/javascript/api-reference/message-persistence-legacy.md). New apps use [Fetch history](#fetch-history) and read `actions`.

Last updated at: 2026-11-09T13:29:01.000Z
