diff --git a/docs.json b/docs.json
index 96952f214..ae74cb471 100644
--- a/docs.json
+++ b/docs.json
@@ -1081,6 +1081,7 @@
"group": "Chat",
"pages": [
"ui-kit/react-native/core-features",
+ "ui-kit/react-native/offline-support",
"ui-kit/react-native/extensions",
"ui-kit/react-native/ai-features"
]
@@ -3485,7 +3486,8 @@
"sdk/react-native/transient-messages",
"sdk/react-native/delivery-read-receipts",
"sdk/react-native/mentions",
- "sdk/react-native/reactions"
+ "sdk/react-native/reactions",
+ "sdk/react-native/offline-support"
]
},
"sdk/react-native/calling-overview",
diff --git a/sdk/react-native/llms-react-native-v4.mdx b/sdk/react-native/llms-react-native-v4.mdx
index cb7a843d8..b76a44026 100644
--- a/sdk/react-native/llms-react-native-v4.mdx
+++ b/sdk/react-native/llms-react-native-v4.mdx
@@ -74,6 +74,7 @@ listeners.
- [Delivery & Read Receipts](/sdk/react-native/delivery-read-receipts)
- [Mentions](/sdk/react-native/mentions)
- [Reactions](/sdk/react-native/reactions)
+- [Offline Support](/sdk/react-native/offline-support)
## Calling
- [Calling — Overview](/sdk/react-native/calling-overview)
diff --git a/sdk/react-native/offline-support.mdx b/sdk/react-native/offline-support.mdx
new file mode 100644
index 000000000..5cefe9f07
--- /dev/null
+++ b/sdk/react-native/offline-support.mdx
@@ -0,0 +1,826 @@
+---
+title: "Offline Support"
+sidebarTitle: "Offline Support"
+description: "Cache conversations and messages on the device, show them instantly, and queue messages sent without a connection with the CometChat React Native SDK."
+---
+
+
+
+| Field | Value |
+| --- | --- |
+| Package | `@cometchat/chat-sdk-react-native` |
+| Import | `import { CometChat } from "@cometchat/chat-sdk-react-native";` |
+| Requirements | React Native 0.74+ · `@op-engineering/op-sqlite` 9.0.0+ (required — offline stays off without it) · `react-native-fs` (media caching) · `react-native-keychain` (encryption only). Native modules — Expo needs a development build or EAS build; Expo Go is not supported |
+| Enable | `new CometChat.AppSettingsBuilder().setOfflineConfig(new CometChat.OfflineConfig({ enabled: true, ... }))` before `CometChat.init()` — offline support is **off by default**; always set `enabled: true` explicitly |
+| Minimum version | `@cometchat/chat-sdk-react-native` 4.2.0 |
+| Key methods | `CometChat.isOfflineEnabled()` · `CometChat.getPendingMessages()` · `CometChat.retryMessage(localMessageId)` / `CometChat.retryAllFailedMessages()` · `CometChat.addSyncListener(id, listener)` / `CometChat.removeSyncListener(id)` · `CometChat.forceSyncNow()` · `CometChat.clearOfflineCache()` · `CometChat.getMediaCachingMode()` · `CometChat.getOfflineEncryptionStatus()` · `CometChat.addOfflineCacheResetListener(id, listener)` |
+| Two-phase reads | Pass a plain-object listener to `fetchPrevious()` / `fetchNext()` — e.g. `{ onCachedMessages, onServerMessages, onError }`; `CometChat.getConversation(with, type, { onCachedConversation, onServerConversation, onError })` |
+| Queued sends | Pass `{ onMessagePending, onMessageSent, onError }` as the second argument of `sendMessage()` / `sendMediaMessage()` / `sendCustomMessage()` |
+| Offline send result | `onMessagePending` (status `PENDING`, `getLocalMessageId()` set), then one `onError(error, reason)` with `reason == null` and `error.errorParams.queued === true`; the Promise rejects with the same error. The message stays queued → `onMessageSent` after reconnect. Not a failure — don't resend. A send **without** a listener is not queued |
+| Failed sends | `onError(error, reason)` with a `CometChat.MessageFailureReason`. A `FAILED` message stays in the outbox; the SDK never retries it on its own — call `retryMessage()` (only `FAILED` messages) or `retryAllFailedMessages()` (`TRANSIENT` only). `DISCARDED` is final |
+| Outbox refusals | Outbox full or media too large: `onError` with code `ERROR_PENDING_QUEUE_LIMIT` and reason `QUEUE_FULL`, `QUEUE_OVERFLOW` or `BYTE_OVERFLOW` |
+| Storage | SQLite database per app ID and user. Kept when the same user logs in again; deleted on logout, on login as a different user and on a failed login |
+| Constraints | A request that uses a server-only filter such as `setSearchKeyword()` gets an empty cache phase, then the server result. Encryption is opt-in and covers the database only, not cached media |
+| Related | [Send Messages](/sdk/react-native/send-message) · [Receive Messages](/sdk/react-native/receive-messages) · [Retrieve Conversations](/sdk/react-native/retrieve-conversations) · [Connection Status](/sdk/react-native/connection-status) |
+
+
+
+Offline support keeps a local copy of the logged-in user's conversations and messages on the device. Your app can render that copy the moment a screen opens — with or without a network — and the SDK replaces it with fresh server data as soon as it arrives. Messages sent while the device is offline are saved in a queue (the outbox) and sent automatically when the connection returns.
+
+Offline support is **opt-in**. Until you enable it, the SDK behaves exactly as it does today: nothing is stored on the device and every read goes to the server.
+
+
+ Offline support is available from React Native SDK **v4.2.0**.
+
+
+## How It Works
+
+- **One local store per user.** The SDK keeps a separate SQLite database for each app ID and user. Logging in again as the same user keeps it; logging out, or logging in as a different user, deletes the previous user's data.
+- **Cached, then server.** When you pass a listener to a read such as `fetchPrevious()`, the SDK first hands you what it has cached, then the server's result. Every server result is written back to the cache.
+- **Queued sends.** A message sent with a `MessageSentListener` is stored in the outbox first. If the device is offline, it stays queued — even if the app is closed — and is sent automatically once the connection is back.
+- **Catch-up sync.** After a reconnect, the SDK sends queued messages, then refreshes the cached conversations and messages so changes made while you were offline (new, edited and deleted messages) reach the cache.
+- **The server wins.** If the cached copy and the server copy of a message differ, the server copy is kept.
+
+## Install the Dependencies
+
+Offline support uses three native modules. They are optional peer dependencies of the SDK, so the SDK installs and runs without them — install the ones you need in your app:
+
+| Package | Version | Needed for |
+| --- | --- | --- |
+| [`@op-engineering/op-sqlite`](https://github.com/OP-Engineering/op-sqlite) | 9.0.0 or later | **Required.** The local database. |
+| [`react-native-fs`](https://github.com/itinance/react-native-fs) | Any | Caching media files on the device. |
+| [`react-native-keychain`](https://github.com/oblador/react-native-keychain) | Any | [Encrypting the cache](#encrypt-the-cache) only. |
+
+Offline support needs **React Native 0.74 or later**, because current versions of `@op-engineering/op-sqlite` require it.
+
+```bash
+npm install @op-engineering/op-sqlite react-native-fs
+cd ios && pod install
+```
+
+Rebuild the app after installing — a JavaScript reload is not enough for native modules.
+
+
+ Without `@op-engineering/op-sqlite`, offline support stays off and the SDK
+ works online only. Nothing is logged, so check `CometChat.isOfflineEnabled()`
+ after login to confirm offline support is running.
+
+
+### Expo
+
+The modules are native code, so they don't run in Expo Go — in Expo Go, offline support stays off. Use a [development build](https://docs.expo.dev/develop/development-builds/introduction/) or an EAS build: install the packages with `npx expo install`, then rebuild. Expo prebuild links the modules automatically, so no config plugin is needed.
+
+## Enable Offline Support
+
+Create a `CometChat.OfflineConfig` with `enabled: true` and pass it to `setOfflineConfig()` on the `AppSettingsBuilder`, then initialize the SDK as usual. Always set `enabled: true` explicitly — the other options have no effect until offline support is enabled.
+
+
+
+ ```typescript
+ let appID: string = "APP_ID";
+ let region: string = "APP_REGION";
+
+ let offlineConfig: CometChat.OfflineConfig = new CometChat.OfflineConfig({
+ enabled: true,
+ maxPendingMessages: 50,
+ mediaCaching: CometChat.MediaCachingMode.AUTO,
+ });
+
+ let appSetting: CometChat.AppSettings = new CometChat.AppSettingsBuilder()
+ .subscribePresenceForAllUsers()
+ .setRegion(region)
+ .setOfflineConfig(offlineConfig)
+ .build();
+
+ CometChat.init(appID, appSetting).then(
+ () => {
+ console.log("Initialization completed successfully");
+ },
+ (error: CometChat.CometChatException) => {
+ console.log("Initialization failed with error:", error);
+ }
+ );
+ ```
+
+
+ ```javascript
+ let appID = "APP_ID";
+ let region = "APP_REGION";
+
+ let offlineConfig = new CometChat.OfflineConfig({
+ enabled: true,
+ maxPendingMessages: 50,
+ mediaCaching: CometChat.MediaCachingMode.AUTO,
+ });
+
+ let appSetting = new CometChat.AppSettingsBuilder()
+ .subscribePresenceForAllUsers()
+ .setRegion(region)
+ .setOfflineConfig(offlineConfig)
+ .build();
+
+ CometChat.init(appID, appSetting).then(
+ () => {
+ console.log("Initialization completed successfully");
+ },
+ (error) => {
+ console.log("Initialization failed with error:", error);
+ }
+ );
+ ```
+
+
+
+The offline store opens when a user logs in (or an existing session is restored) and closes on logout.
+
+### Configuration Options
+
+Set `enabled: true`. Every other option is optional and uses its default when you leave it out. The options are read once at `init()` and stay fixed for the session.
+
+| Option | Type | Default | Description |
+| --- | --- | --- | --- |
+| `enabled` | `boolean` | `false` | Turns offline support on. Nothing is stored on the device unless this is `true`. Always set it explicitly. |
+| `maxConversations` | `number` | `100` | How many conversations the cache keeps. The least recently used are removed first. Range 1–1000. |
+| `maxMessagesPerConversation` | `number` | `500` | How many of the newest messages the cache keeps per conversation. Range 1–1000. |
+| `maxStorageSize` | `number` (bytes) | `262144000` (250 MB) | Space the cached data may use, not counting media. The oldest data is removed first. `0` means no limit. |
+| `cacheDurationDays` | `number` (days) | `0` | Removes a cached conversation that has had no new message and hasn't been opened for this many days. `0` means no time limit. |
+| `maxSnapshotQueries` | `number` | `100` | How many distinct cached lists — users, groups, members, banned members, reactions, notification feed — are kept. The least recently used are removed first. `0` or less uses the default. |
+| `snapshotTtlDays` | `number` (days) | `7` | How long a cached list stays available before it is removed. `0` or less uses the default. |
+| `maxPendingMessages` | `number` | `50` | How many messages waiting to be sent (`PENDING` or `SENDING`) the outbox can hold. `FAILED` messages don't count. Range 1–100. |
+| `pendingQueueOverflowPolicy` | `CometChat.PendingMessageOverflowPolicy` | `REJECT_NEWEST` | What happens when the outbox is full: `REJECT_NEWEST` rejects the new send, `DROP_OLDEST` removes the oldest `PENDING` message to make room. |
+| `autoSendPendingOnReconnect` | `boolean` | `true` | Sends queued messages automatically after a reconnect. It does not affect the catch-up sync. |
+| `mediaCaching` | `CometChat.MediaCachingMode` | `AUTO` | `AUTO` downloads attachments of fetched and received messages so they can be viewed offline. `OFF` caches the message and its attachment details but not the file itself. Sending media works in both modes. |
+| `maxMediaCacheSize` | `number` (bytes) | `524288000` (500 MB) | Space cached media may use. The least recently used files are removed first. `0` means no limit. |
+| `encryption` | `boolean` | `false` | Encrypts the local database. See [Encrypt the Cache](#encrypt-the-cache). |
+| `encryptionFailureBehavior` | `CometChat.EncryptionFailureBehavior` | `ONLINE_ONLY` | Used only when `encryption` is `true` and encryption can't run. `ONLINE_ONLY` keeps no cache and works online only; `PLAINTEXT_FALLBACK` opens an unencrypted cache instead. |
+
+For the options with a range, a value above the maximum — or `0` — is treated as the maximum. An invalid value, such as an unknown enum value, falls back to the default instead of throwing. Unsent messages are never removed to free space.
+
+`OfflineConfig` is immutable — read values back with its getters, such as `isEnabled()` and `getMaxPendingMessages()`.
+
+If you initialize from a settings file, put the same options under `chatSDK.offline`. There too, offline support stays off unless the object sets `enabled: true`.
+
+## Check Whether Offline Is Running
+
+`CometChat.isOfflineEnabled()` returns `true` only while offline support is actually running for the logged-in user: you enabled it, a user is logged in, and the local store is open. It returns `false` before login, after logout, and when CometChat has switched offline support off for your app. Read it each time you need it instead of storing the value.
+
+
+
+ ```typescript
+ let offlineRunning: boolean = CometChat.isOfflineEnabled();
+ ```
+
+
+ ```javascript
+ let offlineRunning = CometChat.isOfflineEnabled();
+ ```
+
+
+
+CometChat can turn offline support off for an app from the server. When that happens, the SDK stops using the local store, deletes the cached data and `isOfflineEnabled()` returns `false`. Your app keeps working online.
+
+## Read From the Cache, Then the Server
+
+Pass a fetch listener to a request's `fetchPrevious()` or `fetchNext()` to receive results in two steps:
+
+1. `onCached*` — what is stored on the device, delivered immediately. It can be an empty list.
+2. `onServer*` — the server's result, which also updates the cache. If the device is offline, or the request fails, `onError` is called instead.
+
+The call still returns a Promise that settles with the server result, so handle the outcome in one place. Without a listener, the request reads from the server only, exactly as before.
+
+### Messages
+
+
+
+ ```typescript
+ let UID: string = "UID";
+ let limit: number = 30;
+
+ let messagesRequest: CometChat.MessagesRequest =
+ new CometChat.MessagesRequestBuilder()
+ .setUID(UID)
+ .setLimit(limit)
+ .build();
+
+ messagesRequest
+ .fetchPrevious({
+ onCachedMessages: (messages: CometChat.BaseMessage[]) => {
+ // Render the cached page right away
+ },
+ onServerMessages: (messages: CometChat.BaseMessage[]) => {
+ // Replace it with the server's page
+ },
+ onError: (error: CometChat.CometChatException) => {
+ // Offline or failed — keep showing the cached page
+ },
+ })
+ .catch(() => {
+ // Already handled in onError
+ });
+ ```
+
+
+ ```javascript
+ let UID = "UID";
+ let limit = 30;
+
+ let messagesRequest = new CometChat.MessagesRequestBuilder()
+ .setUID(UID)
+ .setLimit(limit)
+ .build();
+
+ messagesRequest
+ .fetchPrevious({
+ onCachedMessages: (messages) => {
+ // Render the cached page right away
+ },
+ onServerMessages: (messages) => {
+ // Replace it with the server's page
+ },
+ onError: (error) => {
+ // Offline or failed — keep showing the cached page
+ },
+ })
+ .catch(() => {
+ // Already handled in onError
+ });
+ ```
+
+
+
+### Conversations
+
+
+
+ ```typescript
+ let conversationsRequest: CometChat.ConversationsRequest =
+ new CometChat.ConversationsRequestBuilder().setLimit(30).build();
+
+ conversationsRequest
+ .fetchNext({
+ onCachedConversations: (conversations: CometChat.Conversation[]) => {
+ // Render the cached list
+ },
+ onServerConversations: (conversations: CometChat.Conversation[]) => {
+ // Replace it with the server's list
+ },
+ onError: (error: CometChat.CometChatException) => {
+ // Offline or failed
+ },
+ })
+ .catch(() => {});
+ ```
+
+
+ ```javascript
+ let conversationsRequest = new CometChat.ConversationsRequestBuilder()
+ .setLimit(30)
+ .build();
+
+ conversationsRequest
+ .fetchNext({
+ onCachedConversations: (conversations) => {
+ // Render the cached list
+ },
+ onServerConversations: (conversations) => {
+ // Replace it with the server's list
+ },
+ onError: (error) => {
+ // Offline or failed
+ },
+ })
+ .catch(() => {});
+ ```
+
+
+
+### A Single Conversation
+
+`getConversation()` accepts a third argument. When the conversation is not cached, `onCachedConversation` is called with `undefined`.
+
+
+
+ ```typescript
+ let conversationWith: string = "UID";
+ let conversationType: string = "user";
+
+ CometChat.getConversation(conversationWith, conversationType, {
+ onCachedConversation: (conversation: CometChat.Conversation | undefined) => {},
+ onServerConversation: (conversation: CometChat.Conversation) => {},
+ onError: (error: CometChat.CometChatException) => {},
+ }).catch(() => {});
+ ```
+
+
+ ```javascript
+ CometChat.getConversation("UID", "user", {
+ onCachedConversation: (conversation) => {},
+ onServerConversation: (conversation) => {},
+ onError: (error) => {},
+ }).catch(() => {});
+ ```
+
+
+
+### Other Lists
+
+The same pattern works for these requests:
+
+| Request | Callbacks |
+| --- | --- |
+| `UsersRequest`, `BlockedUsersRequest` | `onCachedUsers` · `onServerUsers` · `onError` |
+| `GroupsRequest` | `onCachedGroups` · `onServerGroups` · `onError` |
+| `GroupMembersRequest` | `onCachedGroupMembers` · `onServerGroupMembers` · `onError` |
+| `BannedMembersRequest` | `onCachedBannedGroupMembers` · `onServerBannedGroupMembers` · `onError` |
+| `ReactionsRequest` | `onCachedReactions` · `onServerReactions` · `onError` |
+| `NotificationFeedRequest` | `onCachedFeedItems` · `onServerFeedItems` · `onError` |
+
+These lists are served from the cache only for a request that was loaded online before with the same filters. A list that has never been loaded online has nothing to show offline.
+
+### When the Cache Is Skipped
+
+The cache only answers a request when it can apply every filter on the request correctly. If a request uses a filter that only the server can evaluate — for example `setSearchKeyword()` — `onCached*` is called with an empty list and the server's result follows. Don't show an empty state for that empty cache page; wait for the server phase.
+
+While offline support is not running, `onCached*` is not called at all and only the server answers.
+
+## Send Messages While Offline
+
+Pass a `MessageSentListener` as the second argument to `sendMessage()`, `sendMediaMessage()` or `sendCustomMessage()`. The listener has three callbacks:
+
+| Callback | When it fires |
+| --- | --- |
+| `onMessagePending(message)` | The message is saved in the outbox. `message.getMessageStatus()` is `CometChat.MessageStatus.PENDING` and `message.getLocalMessageId()` identifies it. Show it in the UI as pending. |
+| `onMessageSent(message)` | The server accepted the message. It carries the same `getLocalMessageId()`, so you can replace the pending copy with this one. |
+| `onError(error, reason)` | Either the message is queued (`reason` is `null` and `error.errorParams.queued` is `true`) or the send failed (`reason` is a `CometChat.MessageFailureReason`). See [Queued or Failed](#queued-or-failed). |
+
+
+
+ ```typescript
+ let receiverID: string = "UID";
+ let receiverType: string = CometChat.RECEIVER_TYPE.USER;
+ let textMessage: CometChat.TextMessage = new CometChat.TextMessage(
+ receiverID,
+ "Hello",
+ receiverType
+ );
+
+ CometChat.sendMessage(textMessage, {
+ onMessagePending: (message: CometChat.BaseMessage) => {
+ // Show the message with a pending indicator,
+ // keyed by message.getLocalMessageId()
+ },
+ onMessageSent: (message: CometChat.BaseMessage) => {
+ // Swap the pending copy (same getLocalMessageId()) for the sent message
+ },
+ onError: (
+ error: CometChat.CometChatException,
+ reason?: CometChat.MessageFailureReason | null
+ ) => {
+ if (reason == null && error.errorParams?.queued === true) {
+ // Queued, not failed — keep the pending indicator.
+ // onMessageSent fires after the connection returns.
+ return;
+ }
+ // The send failed — reason says why
+ },
+ }).catch(() => {
+ // Rejects for a queued send as well as a failed one —
+ // handle the outcome in the listener
+ });
+ ```
+
+
+ ```javascript
+ let receiverID = "UID";
+ let receiverType = CometChat.RECEIVER_TYPE.USER;
+ let textMessage = new CometChat.TextMessage(receiverID, "Hello", receiverType);
+
+ CometChat.sendMessage(textMessage, {
+ onMessagePending: (message) => {
+ // Show the message with a pending indicator,
+ // keyed by message.getLocalMessageId()
+ },
+ onMessageSent: (message) => {
+ // Swap the pending copy (same getLocalMessageId()) for the sent message
+ },
+ onError: (error, reason) => {
+ if (reason == null && error.errorParams?.queued === true) {
+ // Queued, not failed — keep the pending indicator.
+ // onMessageSent fires after the connection returns.
+ return;
+ }
+ // The send failed — reason says why
+ },
+ }).catch(() => {
+ // Rejects for a queued send as well as a failed one —
+ // handle the outcome in the listener
+ });
+ ```
+
+
+
+What happens depends on the connection:
+
+- **Online:** `onMessagePending`, then `onMessageSent`. The Promise resolves with the sent message.
+- **Offline:** `onMessagePending`, then one `onError` with `reason` `null` — the queued notice — and the Promise rejects with the same error. The message stays `PENDING` in the outbox and is sent after the connection returns, when `onMessageSent` fires. The same notice fires once if the device is online but the first attempt gets no answer. Do not resend the message yourself, or the recipient receives it twice.
+- **Outbox full:** with the default `REJECT_NEWEST` policy, the new send fails with `onError`, the code `ERROR_PENDING_QUEUE_LIMIT` and the reason `QUEUE_FULL`. With `DROP_OLDEST`, the oldest `PENDING` message is removed instead and its own `onError` fires with the same code and the reason `QUEUE_OVERFLOW`. A media message too large for `maxMediaCacheSize` fails with the reason `BYTE_OVERFLOW`.
+
+
+ Only sends that pass a `MessageSentListener` are queued. A send without a
+ listener behaves exactly as before: while offline, its Promise rejects and
+ the message is not saved.
+
+
+Treat the message as pending from the moment `onMessagePending` fires, and use `getLocalMessageId()` to match it to the message you later get in `onMessageSent` or `onError`. The local ID is assigned by the SDK and is separate from any `muid` you set.
+
+The listener lives in memory only. If the app restarts while a message is queued, the message is still sent on reconnect, but the old listener is gone — call [`getPendingMessages()`](#get-pending-messages) on start-up to show queued messages again, and use [`addOfflineMessageListener()`](#other-offline-methods) to follow them.
+
+### Queued or Failed
+
+`onError` — and the Promise's rejection — covers two different outcomes. Tell them apart before you show an error:
+
+| | Queued | Failed |
+| --- | --- | --- |
+| `reason` in `onError` | `null` | A `CometChat.MessageFailureReason` |
+| `error.errorParams` | `{ queued: true, status: "PENDING", localMessageId }` | No `queued` flag |
+| `error.code` | `FAILED_TO_FETCH`, the usual not-connected code | Depends on the cause, for example `ERROR_PENDING_QUEUE_LIMIT` |
+| Message status | `PENDING` | `FAILED`, or removed for `QUEUE_FULL`, `QUEUE_OVERFLOW`, `BYTE_OVERFLOW` and `DISCARDED` |
+| What follows | `onMessageSent` after reconnect | Nothing, unless you retry it |
+
+On the Promise rejection, which has no `reason`, check `error.errorParams?.queued === true`.
+
+### Get Pending Messages
+
+`CometChat.getPendingMessages()` resolves with the messages that are still in the outbox — pending, sending and failed — oldest first, each with `getLocalMessageId()` set. Use it to show unsent messages again after the app restarts. `message.getMessageStatus()` tells them apart: `CometChat.MessageStatus.PENDING` or `SENDING` for a message waiting to be sent, `FAILED` for one that needs a retry.
+
+
+
+ ```typescript
+ CometChat.getPendingMessages().then((messages: CometChat.BaseMessage[]) => {
+ messages.forEach((message: CometChat.BaseMessage) => {
+ if (message.getMessageStatus() === CometChat.MessageStatus.FAILED) {
+ // Show the message with a failed indicator and a retry action
+ } else {
+ // Show the message with a pending indicator
+ }
+ });
+ });
+ ```
+
+
+ ```javascript
+ CometChat.getPendingMessages().then((messages) => {
+ messages.forEach((message) => {
+ if (message.getMessageStatus() === CometChat.MessageStatus.FAILED) {
+ // Show the message with a failed indicator and a retry action
+ } else {
+ // Show the message with a pending indicator
+ }
+ });
+ });
+ ```
+
+
+
+It resolves with an empty list while offline support is not running.
+
+## Failed Messages and Retry
+
+A queued message is `PENDING` until it is sent. While the device is offline it stays `PENDING` for as long as it takes — it does not fail. The SDK sends it when the connection returns; a send that gets no answer in time (15 seconds, or 120 seconds for media) counts as no answer and the message stays `PENDING` for the next reconnect.
+
+A message ends with `onError` and one of these reasons:
+
+| Reason | When | Message afterwards | Retry |
+| --- | --- | --- | --- |
+| `TRANSIENT` | The server was overloaded or rate-limited the request (HTTP 5xx or 429), or the message got no answer on 5 reconnects in a row (code `ERROR_OUTBOX_AUTO_FLUSH_CAP_REACHED`). | `FAILED`, in the outbox | `retryMessage()` or `retryAllFailedMessages()` |
+| `PERMANENT` | The server rejected it — for example, the receiver has blocked the user or the user lacks permission. | `FAILED`, in the outbox | `retryMessage()`, once the cause is fixed |
+| `QUEUE_FULL`, `QUEUE_OVERFLOW`, `BYTE_OVERFLOW` | The outbox refused or evicted it (code `ERROR_PENDING_QUEUE_LIMIT`). | Removed | Send it again as a new message |
+| `DISCARDED` | It was removed before it was sent — by `clearOfflineCache()`, or because its conversation was removed from the cache (for example, the conversation was deleted or the user left the group). Code `ERROR_OFFLINE_MESSAGE_DISCARDED`. | Removed | None — final |
+
+The per-send `onError` fires if the listener is still in memory, and `onOutboxMessageFailed` fires on every [offline message listener](#other-offline-methods). A failed message steps aside — the next message in the conversation is still sent.
+
+**The SDK never retries a `FAILED` message on its own.** It stays in the outbox until you retry it and it is sent, or the cache is cleared. It doesn't count toward `maxPendingMessages`. Let the user decide, then call `CometChat.retryMessage()` with the message's local ID:
+
+
+
+ ```typescript
+ let localId: string | undefined = failedMessage.getLocalMessageId();
+
+ if (localId) {
+ // Show the message as pending again, then retry it
+ CometChat.retryMessage(localId, {
+ onMessageSent: (message: CometChat.BaseMessage) => {
+ // Sent — replace the failed copy
+ },
+ onError: (
+ error: CometChat.CometChatException,
+ reason?: CometChat.MessageFailureReason | null
+ ) => {
+ if (reason == null && error.errorParams?.queued === true) {
+ // Offline — queued again, sent on reconnect
+ return;
+ }
+ // Failed again, or not retryable
+ // (code ERROR_OFFLINE_MESSAGE_NOT_RETRYABLE)
+ },
+ });
+ }
+ ```
+
+
+ ```javascript
+ let localId = failedMessage.getLocalMessageId();
+
+ if (localId) {
+ // Show the message as pending again, then retry it
+ CometChat.retryMessage(localId, {
+ onMessageSent: (message) => {
+ // Sent — replace the failed copy
+ },
+ onError: (error, reason) => {
+ if (reason == null && error.errorParams?.queued === true) {
+ // Offline — queued again, sent on reconnect
+ return;
+ }
+ // Failed again, or not retryable
+ // (code ERROR_OFFLINE_MESSAGE_NOT_RETRYABLE)
+ },
+ });
+ }
+ ```
+
+
+
+`retryMessage()` only accepts a `FAILED` message. For any other local ID — a message that is still pending, already sent, or unknown — it ends with `onError` and the code `ERROR_OFFLINE_MESSAGE_NOT_RETRYABLE` (`CometChat.OfflineErrorCode.MESSAGE_NOT_RETRYABLE`) and sends nothing, so it can't create a duplicate.
+
+Without a listener, `retryMessage(localId)` returns a Promise that resolves with the sent message. If the device is offline, the message goes back to `PENDING`, the listener or Promise gets the same queued notice as a new send, and the message is sent on reconnect.
+
+`CometChat.retryAllFailedMessages()` retries every failed message whose reason is `TRANSIENT`. Messages the server rejected (`PERMANENT`) are skipped, because they would fail again — retry those one at a time with `retryMessage()` once the cause is fixed. Pass a `MessageSentListener` to follow each retried message.
+
+## Listen for Sync Events
+
+After a reconnect, the SDK runs a catch-up sync in two phases: conversations, then messages. Register a sync listener to know when each phase starts, finishes or fails — for example, to refresh a screen once the sync is complete.
+
+
+
+ ```typescript
+ let listenerID: string = "UNIQUE_LISTENER_ID";
+
+ CometChat.addSyncListener(listenerID, {
+ onConversationSyncStarted: () => {},
+ onConversationSyncCompleted: () => {
+ // Re-fetch the conversation list
+ },
+ onConversationSyncError: (error: CometChat.CometChatException) => {},
+ onMessageSyncStarted: () => {},
+ onMessageSyncCompleted: (
+ messages: CometChat.BaseMessage[],
+ outcome: CometChat.SyncOutcome
+ ) => {
+ // Re-fetch the open message list
+ },
+ onMessageSyncError: (error: CometChat.CometChatException) => {},
+ });
+
+ // Remove it when you no longer need it
+ CometChat.removeSyncListener(listenerID);
+ ```
+
+
+ ```javascript
+ let listenerID = "UNIQUE_LISTENER_ID";
+
+ CometChat.addSyncListener(listenerID, {
+ onConversationSyncStarted: () => {},
+ onConversationSyncCompleted: () => {
+ // Re-fetch the conversation list
+ },
+ onConversationSyncError: (error) => {},
+ onMessageSyncStarted: () => {},
+ onMessageSyncCompleted: (messages, outcome) => {
+ // Re-fetch the open message list
+ },
+ onMessageSyncError: (error) => {},
+ });
+
+ // Remove it when you no longer need it
+ CometChat.removeSyncListener(listenerID);
+ ```
+
+
+
+`onConversationSyncCompleted` receives the refreshed conversations. `onMessageSyncCompleted` receives the new and updated messages and a `CometChat.SyncOutcome`: `CAUGHT_UP` when the sync fetched every change and the cache is up to date, or `PARTIAL` when it stopped early and some changes remain for a later sync. Each callback is optional.
+
+The sync waits for the outbox to send queued messages, for up to 20 seconds, before it starts.
+
+
+ Catching up on edits, deletes and reactions made while the device was offline
+ relies on a CometChat server capability. If it isn't available for your app
+ yet, every sync reports `CAUGHT_UP` and those changes are not picked up until
+ the messages are fetched again. Sending, reading and the cache work either
+ way. Contact CometChat support if you're not sure.
+
+
+## Other Offline Methods
+
+All of these are static methods on `CometChat`. They are safe to call while offline support is not running, and return the empty value shown.
+
+| Method | Returns | Description |
+| --- | --- | --- |
+| `forceSyncNow(callback?)` | `Promise` | Runs the catch-up sync now instead of waiting for a reconnect. Pass `{ onSuccess, onError }` to learn why a sync didn't run: `ERROR_OFFLINE_DISABLED`, `ERROR_USER_NOT_LOGGED_IN`, `ERROR_OFFLINE_SYNC_IN_PROGRESS`, or `ERROR_OFFLINE_SYNC_COOLDOWN` within 10 seconds of the start of the last sync, including one started by a reconnect (`error.details.retryAfterMs` says when to try again). Without a callback, these cases resolve without syncing. |
+| `getSyncState()` | `CometChat.SyncState` | `SYNCING` while a catch-up sync runs, otherwise `IDLE`. |
+| `getLastSyncTimestamp()` | `Promise` | Server time of the last completed sync, in **seconds** since the epoch, or `0`. Multiply by 1000 before passing it to `new Date()`. |
+| `getCacheSize()` | `Promise` | Size of the offline cache in bytes — the local database plus cached media — or `0`. |
+| `clearOfflineCache()` | `Promise` | Deletes all cached data and media while the user stays logged in. Every `PENDING` or `SENDING` message ends with `onError` and the reason `DISCARDED`, and is not sent; `FAILED` messages are removed without a callback. Offline support keeps running with an empty cache. |
+| `downloadMedia(message, listener?)` | `Promise` | Downloads a media message's attachment to the cache, or returns it if it is already cached. Resolves with the local file path, or `null`. |
+| `getCachedMediaPath(message)` | `Promise` | The local file path of a cached attachment, or `null` if it isn't cached. Never downloads. |
+| `getMediaCachingMode()` | `CometChat.MediaCachingMode` | The media caching mode in effect: your `mediaCaching` value while offline support is running, and `OFF` whenever it isn't. Fixed for the session. `getMediaCaching()` is a deprecated alias that returns the same value. |
+| `addMediaDownloadListener(id, listener)` / `removeMediaDownloadListener(id)` | `Promise` / `void` | An app-wide observer for background media downloads: `onMediaDownloaded(localMessageId, attachmentId, localPath)`, `onMediaProgress(localMessageId, attachmentId, percent)`, `onMediaDownloadFailed(localMessageId, attachmentId, error)`. |
+| `addOfflineMessageListener(id, listener)` / `removeOfflineMessageListener(id)` | `void` | An app-wide observer for every outbox message, including ones queued before a restart: `onOutboxMessagePending(message)`, `onOutboxMessageSent(message)`, `onOutboxMessageFailed(message, reason)`. |
+| `addOfflineCacheResetListener(id, listener)` / `removeOfflineCacheResetListener(id)` | `void` | Tells you when the SDK had to delete and rebuild the cache on its own. See [Cache Resets](#cache-resets). |
+
+## Encrypt the Cache
+
+Set `encryption: true` to encrypt the local database with SQLCipher. Encryption is off by default and needs two extra pieces in your app:
+
+1. Turn on SQLCipher for op-sqlite in your app's `package.json`, then run `pod install` and rebuild the app for both iOS and Android:
+
+ ```json
+ {
+ "op-sqlite": {
+ "sqlcipher": true
+ }
+ }
+ ```
+
+ op-sqlite reads this flag when the native app is built. In Expo, add it before you run `npx expo prebuild` or an EAS build; Expo Go can't use it.
+
+2. Install [`react-native-keychain`](https://github.com/oblador/react-native-keychain), where the SDK keeps the encryption key:
+
+ ```bash
+ npm install react-native-keychain
+ ```
+
+ To keep the key somewhere else, pass your own key vault to `CometChat.setOfflineKeyVault()` before `CometChat.init()` instead — see [Use Your Own Key Vault](#use-your-own-key-vault).
+
+
+
+ ```typescript
+ let offlineConfig: CometChat.OfflineConfig = new CometChat.OfflineConfig({
+ enabled: true,
+ encryption: true,
+ });
+ ```
+
+
+ ```javascript
+ let offlineConfig = new CometChat.OfflineConfig({
+ enabled: true,
+ encryption: true,
+ });
+ ```
+
+
+
+`CometChat.getOfflineEncryptionStatus()` reports what is actually in effect — `enabled` is what you asked for, and `atRest`, `keyStorage` and `cipherAvailable` describe what the SDK achieved. Check it after login. With encryption working it looks like this:
+
+```json
+{
+ "enabled": true,
+ "atRest": "SQLCIPHER",
+ "keyStorage": "KEYSTORE",
+ "cipherAvailable": true,
+ "cipher": "4.6.1 community",
+ "leakage": "Downloaded and queued media files are stored unencrypted."
+}
+```
+
+`cipher` is the SQLCipher version in your build, and `keyStorage` is `"KEYSTORE"` for `react-native-keychain` or `"SOFTWARE"` for your own key vault.
+
+If encryption can't run, `atRest` is `"NONE"` and `keyStorage` is `"NONE"`. `cipherAvailable: false` means SQLCipher isn't in the build; `cipherAvailable: true` means there is no key store — install `react-native-keychain` or set a key vault. By default (`encryptionFailureBehavior: ONLINE_ONLY`) the SDK then does not write an unencrypted cache — offline support stays off and the app works online only. Set `encryptionFailureBehavior` to `CometChat.EncryptionFailureBehavior.PLAINTEXT_FALLBACK` only if keeping offline support matters more than encrypting the cache; the SDK then opens an unencrypted cache.
+
+Cached media files are not encrypted. Set `mediaCaching: CometChat.MediaCachingMode.OFF` if attachments must not be stored on the device. This is not end-to-end encryption; it only affects the copy stored on the device.
+
+Encryption is read once at `init()`. If you change `encryption` for an app that already has a cache, the SDK deletes the old cache and builds a new one, and tells you through a [cache reset](#cache-resets).
+
+### Use Your Own Key Vault
+
+`react-native-keychain` is detected automatically. To store the key elsewhere, pass an object with `get`, `set`, `delete` and `kind` to `CometChat.setOfflineKeyVault()` before `CometChat.init()`. The SDK stores a 64-character hex key under a service name, and never exposes the key through a public API. Pass `null` to go back to automatic detection.
+
+
+
+ ```typescript
+ CometChat.setOfflineKeyVault({
+ get: async (service: string): Promise => {
+ // Return the stored key for this service, or null
+ return null;
+ },
+ set: async (service: string, hexKey: string): Promise => {
+ // Store the key for this service, replacing any existing one
+ },
+ delete: async (service: string): Promise => {
+ // Remove the key; resolve even if nothing is stored
+ },
+ kind: () => "SOFTWARE",
+ });
+ ```
+
+
+ ```javascript
+ CometChat.setOfflineKeyVault({
+ get: async (service) => {
+ // Return the stored key for this service, or null
+ return null;
+ },
+ set: async (service, hexKey) => {
+ // Store the key for this service, replacing any existing one
+ },
+ delete: async (service) => {
+ // Remove the key; resolve even if nothing is stored
+ },
+ kind: () => "SOFTWARE",
+ });
+ ```
+
+
+
+## Cache Resets
+
+If the SDK can't use the existing cache — the encryption key was lost or doesn't match, the database is corrupted or keeps failing to open, or you changed the `encryption` setting — it deletes the cache and starts a new one. Queued messages in the old cache are lost. Register an offline cache reset listener to find out:
+
+
+
+ ```typescript
+ let listenerID: string = "UNIQUE_LISTENER_ID";
+
+ CometChat.addOfflineCacheResetListener(listenerID, {
+ onOfflineCacheReset: (
+ reason: CometChat.OfflineCacheResetReason,
+ pendingMessagesLost: number
+ ) => {
+ if (pendingMessagesLost === CometChat.OFFLINE_PENDING_COUNT_UNKNOWN) {
+ // Unsent messages may have been lost, but they couldn't be counted
+ }
+ },
+ });
+
+ // Remove it when you no longer need it
+ CometChat.removeOfflineCacheResetListener(listenerID);
+ ```
+
+
+ ```javascript
+ let listenerID = "UNIQUE_LISTENER_ID";
+
+ CometChat.addOfflineCacheResetListener(listenerID, {
+ onOfflineCacheReset: (reason, pendingMessagesLost) => {
+ if (pendingMessagesLost === CometChat.OFFLINE_PENDING_COUNT_UNKNOWN) {
+ // Unsent messages may have been lost, but they couldn't be counted
+ }
+ },
+ });
+
+ // Remove it when you no longer need it
+ CometChat.removeOfflineCacheResetListener(listenerID);
+ ```
+
+
+
+`reason` is one of `CometChat.OfflineCacheResetReason`: `KEY_LOST`, `WRONG_KEY`, `CORRUPTED`, `ESCALATED` (the cache failed to open repeatedly) or `ENCRYPTION_CHANGED`. `pendingMessagesLost` is the number of unsent messages that were lost, or `-1` (`CometChat.OFFLINE_PENDING_COUNT_UNKNOWN`) when they couldn't be counted — it doesn't mean none were lost. Resets happen when the store opens — at login, or when a saved session is restored — and are not replayed, so register the listener before you call `CometChat.init()`.
+
+## Logging Out and Switching Users
+
+| Event | What happens to the local store |
+| --- | --- |
+| Logout, or the session ends | Deleted — cached data, cached media and queued messages. Queued messages are discarded without a callback. |
+| Login as the same user again (for example, with a new auth token) | Kept, including queued messages, which are sent once the user is connected. |
+| Login as a different user | The previous user's data is deleted before the new user's store opens. |
+| A login that fails | The previous user's data is deleted, as on logout — the user ends up logged out. |
+
+Do not clear the cache yourself on logout; use `clearOfflineCache()` only to empty the cache while the user stays logged in.
+
+## Limitations
+
+- The cache holds what your app has loaded while online, plus the changes picked up by the catch-up sync. Older history that was never loaded is not available offline.
+- Only messages sent with a `MessageSentListener` are queued offline. Other actions — such as editing, deleting, reacting, pinning or marking as read — need a connection.
+- Queued and failed messages are discarded on logout and by `clearOfflineCache()`. `clearOfflineCache()` reports each queued message as `DISCARDED`; logout does not.
+- Failed messages are not retried automatically — your app decides when to call `retryMessage()` or `retryAllFailedMessages()`.
+
+---
+
+## Next Steps
+
+
+
+ Send text, media and custom messages
+
+
+ Listen for messages and fetch message history
+
+
+ Fetch and paginate the conversation list
+
+
+ Track the WebSocket connection
+
+
diff --git a/ui-kit/react-native/core-features.mdx b/ui-kit/react-native/core-features.mdx
index 9fb0f61c2..77df14b20 100644
--- a/ui-kit/react-native/core-features.mdx
+++ b/ui-kit/react-native/core-features.mdx
@@ -13,7 +13,7 @@ description: "Explore CometChat React Native UI Kit core messaging features, inc
| Where they render | `CometChatMessageList` and `CometChatMessageComposer` render these themselves |
| Turning one OFF | every one has a `hide*` prop on its component (e.g. `hideReactionOption`, `hideReplyInThreadOption`) — see [Message List](/ui-kit/react-native/message-list) |
| Common mistake | Enabling the legacy **Reactions** or **Mentions** dashboard extension. Both are already core in v5 and those extensions are scheduled for deprecation — enabling them is unnecessary |
-| Related | [Message List](/ui-kit/react-native/message-list) · [Message Composer](/ui-kit/react-native/message-composer) · [Extensions](/ui-kit/react-native/extensions) |
+| Related | [Message List](/ui-kit/react-native/message-list) · [Message Composer](/ui-kit/react-native/message-composer) · [Extensions](/ui-kit/react-native/extensions) · [Offline Support](/ui-kit/react-native/offline-support) (opt-in, off by default) |
@@ -285,6 +285,16 @@ CometChat facilitates Group Chats, allowing users to have conversations with mul
For a comprehensive understanding and guide on implementing and using the Groups feature in CometChat, refer to the detailed guide on [Groups](/ui-kit/react-native/groups).
+## Offline Support
+
+With offline support turned on, conversations and messages load from a cache on the device first and refresh from the server when it is reachable. Messages sent without a connection appear right away and are sent automatically when the device reconnects. Offline support is off by default — turn it on with the `offline` field of `UIKitSettings`, and install the native modules it needs. See [Offline Support](/ui-kit/react-native/offline-support) for setup and limitations.
+
+| Components | Functionality |
+| --- | --- |
+| [Conversations](/ui-kit/react-native/conversations) | Renders the cached conversation list immediately, then updates it with the server's list. Marks a conversation that has a queued (clock icon) or failed (error icon) message. |
+| [MessageList](/ui-kit/react-native/message-list) | Opens a conversation with its cached messages and media, then updates it with the server's page. |
+| [MessageComposer](/ui-kit/react-native/message-composer) | Queues messages sent while offline; they show a clock icon and are delivered automatically when the connection returns. A message that fails is not retried automatically. |
+
---
## Next Steps
diff --git a/ui-kit/react-native/llms-react-native-v5.mdx b/ui-kit/react-native/llms-react-native-v5.mdx
index 0e0233d05..c471e81aa 100644
--- a/ui-kit/react-native/llms-react-native-v5.mdx
+++ b/ui-kit/react-native/llms-react-native-v5.mdx
@@ -74,6 +74,7 @@ components, theming tokens, or feature enablement.
## Core & configuration
- [Core Features](/ui-kit/react-native/core-features)
+- [Offline Support](/ui-kit/react-native/offline-support)
- [Methods](/ui-kit/react-native/methods)
- [Events](/ui-kit/react-native/events)
- [Sound Manager](/ui-kit/react-native/sound-manager)
diff --git a/ui-kit/react-native/offline-support.mdx b/ui-kit/react-native/offline-support.mdx
new file mode 100644
index 000000000..90fcdb916
--- /dev/null
+++ b/ui-kit/react-native/offline-support.mdx
@@ -0,0 +1,186 @@
+---
+title: "Offline Support"
+description: "Let the CometChat React Native UI Kit show cached conversations and messages without a network and send queued messages when the connection returns."
+---
+
+
+
+| Field | Value |
+| --- | --- |
+| Package | `@cometchat/chat-uikit-react-native` v5.x |
+| Extra deps | `@react-native-community/netinfo` (required by the UI Kit) · `@op-engineering/op-sqlite` 9.0.0+ (required for offline) · `react-native-fs` (media caching) · `react-native-keychain` (encryption only) — native modules, so Expo needs a development build or EAS build; Expo Go is not supported |
+| Enable | Add `offline: { enabled: true, ... }` — or `offline: true`, or a `CometChat.OfflineConfig` — to the `UIKitSettings` passed to `CometChatUIKit.init()`. Offline support is **off by default**; always set `enabled: true` in an options object |
+| Config | The value is converted with the React Native SDK's `OfflineConfig` — options are listed in [React Native SDK Offline Support](/sdk/react-native/offline-support#configuration-options) |
+| Runtime check | `CometChat.isOfflineEnabled()` from `@cometchat/chat-sdk-react-native` |
+| Components that use the cache | `CometChatConversations` → [Conversations](/ui-kit/react-native/conversations), `CometChatMessageList` → [Message List](/ui-kit/react-native/message-list), `CometChatMessageComposer` → [Message Composer](/ui-kit/react-native/message-composer) |
+| Component props | None — offline support is turned on in UI Kit settings only |
+| Message states | A queued message shows a clock icon; a failed one shows an error icon — in the message list and on the conversation's row. A message discarded before it was sent is final |
+| Not included | No offline banner, no automatic retry of failed messages and no "retry" action for a failed message |
+
+
+
+With offline support turned on, the UI Kit keeps showing the user's chats when the network drops. Conversations and messages load from a cache on the device first and are refreshed from the server when it is reachable, and messages sent while offline are delivered automatically once the connection returns.
+
+The UI Kit builds on the [offline support in the React Native SDK](/sdk/react-native/offline-support). You turn it on in UI Kit settings; the components handle the rest.
+
+{/* TODO: replace v5.x.x with the first UI Kit release that ships offline support (UI Kit PR #1434) before this page goes live. */}
+
+ Offline support is available from React Native UI Kit **v5.x.x** with React Native SDK **v4.2.0** or later.
+
+
+## Install the Dependencies
+
+| Package | Version | Needed for |
+| --- | --- | --- |
+| `@react-native-community/netinfo` | Any | **Required by the UI Kit**, even with offline support off — the Message List uses it to detect when the network returns, and the app bundle fails to build without it. |
+| `@op-engineering/op-sqlite` | 9.0.0 or later | **Required for offline support.** The SDK's local database. |
+| `react-native-fs` | Any | Caching media files on the device. |
+| `react-native-keychain` | Any | [Encrypting the cache](/sdk/react-native/offline-support#encrypt-the-cache) only. |
+
+Offline support needs **React Native 0.74 or later**, because current versions of `@op-engineering/op-sqlite` require it.
+
+```bash
+npm install @react-native-community/netinfo @op-engineering/op-sqlite react-native-fs
+cd ios && pod install
+```
+
+These are native modules, so rebuild the app after installing them. In an Expo app, install them with `npx expo install` and rebuild your [development build](https://docs.expo.dev/develop/development-builds/introduction/) or EAS build — they don't run in Expo Go, where offline support stays off.
+
+
+ Without `@op-engineering/op-sqlite`, offline support stays off and the UI Kit
+ works online only. Nothing is logged, so check `CometChat.isOfflineEnabled()`
+ after login to confirm offline support is running.
+
+
+## Enable Offline Support
+
+Add the `offline` field to your `UIKitSettings`:
+
+
+
+```tsx
+import { CometChatUIKit, UIKitSettings } from "@cometchat/chat-uikit-react-native";
+import { CometChat } from "@cometchat/chat-sdk-react-native";
+
+const uiKitSettings: UIKitSettings = {
+ appId: "APP_ID",
+ authKey: "AUTH_KEY",
+ region: "REGION",
+ subscriptionType: CometChat.AppSettings.SUBSCRIPTION_TYPE_ALL_USERS as UIKitSettings["subscriptionType"],
+ offline: {
+ enabled: true,
+ mediaCaching: CometChat.MediaCachingMode.AUTO,
+ },
+};
+
+CometChatUIKit.init(uiKitSettings)
+ .then(() => {
+ console.log("CometChat UI Kit initialized with offline support.");
+ })
+ .catch((error) => {
+ console.error("CometChat UI Kit initialization failed:", error);
+ });
+```
+
+
+```jsx
+import { CometChatUIKit } from "@cometchat/chat-uikit-react-native";
+import { CometChat } from "@cometchat/chat-sdk-react-native";
+
+const uiKitSettings = {
+ appId: "APP_ID",
+ authKey: "AUTH_KEY",
+ region: "REGION",
+ subscriptionType: CometChat.AppSettings.SUBSCRIPTION_TYPE_ALL_USERS,
+ offline: {
+ enabled: true,
+ mediaCaching: CometChat.MediaCachingMode.AUTO,
+ },
+};
+
+CometChatUIKit.init(uiKitSettings)
+ .then(() => {
+ console.log("CometChat UI Kit initialized with offline support.");
+ })
+ .catch((error) => {
+ console.error("CometChat UI Kit initialization failed:", error);
+ });
+```
+
+
+
+`offline` accepts three forms:
+
+| Value | Result |
+| --- | --- |
+| `true` | Offline support with the SDK's default options. |
+| An options object | The options you set, with defaults for the rest. Include `enabled: true` — without it, `enabled` defaults to `false` and offline support stays off. Every option in the [SDK configuration table](/sdk/react-native/offline-support#configuration-options) is supported. |
+| A `CometChat.OfflineConfig` | Passed to the SDK as is. Create it with `enabled: true`. |
+
+If you leave `offline` out, or set it to `false`, offline support stays off and the UI Kit works exactly as before.
+
+If you initialize with `CometChatUIKit.initFromSettings()`, put the same value under `chatSDK.offline` in the settings object instead:
+
+```json
+{
+ "chatSDK": {
+ "offline": { "enabled": true }
+ }
+}
+```
+
+
+ The UI Kit applies the `offline` setting only when the installed
+ `@cometchat/chat-sdk-react-native` exposes `CometChat.OfflineConfig`. With
+ an older SDK the setting is silently ignored and the UI Kit works online
+ only — check `CometChat.isOfflineEnabled()` after login if you need to be
+ sure.
+
+
+
+ Whether offline support is actually running can change after init — for
+ example, CometChat can switch it off for your app from the server. The UI
+ Kit checks `CometChat.isOfflineEnabled()` each time it needs to; do the same
+ in your own code instead of reading back the value you configured.
+
+
+## What Changes in the Components
+
+| Feature | Components | Behaviour |
+| --- | --- | --- |
+| Cached conversations | [Conversations](/ui-kit/react-native/conversations) | The list renders from the cache immediately, then updates with the server's list. Offline, the cached list stays on screen. |
+| Cached messages | [Message List](/ui-kit/react-native/message-list) | A conversation opens with its cached messages, then updates with the server's page. |
+| Sending while offline | [Message Composer](/ui-kit/react-native/message-composer), [Message List](/ui-kit/react-native/message-list) | A message sent offline appears in the list straight away with a clock icon and is queued — even if the app is closed. The SDK's "queued" notice is not shown as an error: the message keeps its clock icon. It is sent automatically when the connection returns, and its receipt updates as usual. |
+| Pending and failed messages | [Message List](/ui-kit/react-native/message-list), [Conversations](/ui-kit/react-native/conversations) | A queued message shows a clock icon in its bubble, and a message that failed to send shows an error icon. A failed message is not retried automatically, including when the connection returns. A message discarded before it was sent — for example, when the offline cache is cleared — is final and is not sent. The conversation's row shows the same icon in place of the last message's receipt while it has a queued or failed message. The icons follow your receipt styles (`waitIcon`, `errorIcon`). |
+| Cached media | [Message List](/ui-kit/react-native/message-list) | Images, videos, audio and files that were downloaded while online are shown from the device when offline (with `mediaCaching` set to `AUTO`, the default). The UI Kit follows `CometChat.getMediaCachingMode()`, so nothing is cached while offline support isn't running. |
+| Attachment uploads | [Message Composer](/ui-kit/react-native/message-composer) | A file that failed to upload can be retried, and staged attachments are restored when the user reopens the conversation. |
+
+## Logging Out
+
+The SDK deletes the cached data, cached media and any queued messages when the user logs out, when a different user logs in, and when a login fails. Logging in again as the same user keeps the cache and any queued messages. You don't need to clear anything yourself.
+
+## Limitations
+
+- The cache only holds what the user has loaded while online, plus the changes picked up when the device reconnects. A user who has never logged in online has nothing to show offline.
+- Only sending messages works offline. Actions such as editing, deleting, reacting and pinning need a connection.
+- Search reads from the server only, so it doesn't work while offline.
+- Failed messages are never retried automatically, and there is no built-in offline banner or "retry" action for a failed message. Use `CometChat.isOfflineEnabled()`, the SDK's [connection status](/sdk/react-native/connection-status) and its [retry methods](/sdk/react-native/offline-support#failed-messages-and-retry) if you want to build your own.
+
+---
+
+## Next Steps
+
+
+
+ Configuration options, queued sends and sync events
+
+
+ See how the components power each chat feature
+
+
+ Customize how messages are rendered
+
+
+ Initialize, log in and send messages with the UI Kit
+
+