> ## Documentation Index
> Fetch the complete documentation index at: https://www.cometchat.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Media Caching

> Cache image, video, audio and file attachments on the device so media stays viewable offline with the CometChat React Native Chat SDK.

> [accordion] AI Integration Quick Reference
>
> | Field | Value |
> | - | - |
> | Install | `react-native-fs` (peer dependency) — without it media is not cached |
> | Mode (init, fixed for the session) | `new CometChat.OfflineConfig({ enabled: true, mediaCaching: CometChat.MediaCachingMode.AUTO })` — `AUTO` (default) or `OFF` |
> | Budget | `maxMediaCacheSize` (bytes, default 500 MB, `0` = unlimited) |
> | Read the mode | `CometChat.getMediaCachingMode()` — `OFF` whenever offline is not active |
> | Cached path | `attachment.getLocalPath()` · `await CometChat.getCachedMediaPath(attachment)` |
> | Download on demand | `await CometChat.downloadMedia(attachment, listener?)` → `string \| null` |
> | Background downloads | `await CometChat.addMediaDownloadListener(id, listener)` / `removeMediaDownloadListener(id)` |
>
> ```typescript theme={null}
> const offlineConfig = new CometChat.OfflineConfig({
>   enabled: true,
>   mediaCaching: CometChat.MediaCachingMode.AUTO,
>   maxMediaCacheSize: 500 * 1024 * 1024,
> });
>
> const attachment = mediaMessage.getAttachment();
> const localPath = attachment.getLocalPath();                       // set when bytes are cached
> const cached = await CometChat.getCachedMediaPath(attachment);     // null if not cached
>
> const path = await CometChat.downloadMedia(attachment, {
>   onMediaProgress: (messageId, attachmentKey, percent) => {},
>   onMediaDownloaded: (messageId, attachmentKey, localPath) => {},
>   onMediaDownloadFailed: (messageId, attachmentKey, error) => {},
> });
>
> await CometChat.addMediaDownloadListener("MEDIA_LISTENER", {
>   onMediaDownloaded: (messageId, attachmentKey, localPath) => {},
>   onMediaDownloadFailed: (messageId, attachmentKey, error) => {},
> });
> ```

With offline support on, the SDK can cache attachment bytes durably on the device so media stays viewable offline — even after the server's presigned URL has expired. This page covers the caching modes, what downloads automatically, how to read cached bytes, and how to download media on demand.

> [!NOTE]
>
> Media caching applies only while offline support is active for the logged-in user (`CometChat.isOfflineEnabled()` is `true`), and needs the optional `react-native-fs` peer dependency. See [Enable Offline Support](https://www.cometchat.com/docs/sdk/react-native/offline-enable.md).

> [!WARNING]
>
> On recent React Native versions, `react-native-fs` 2.20.0 can crash the app on Android when a download loses the network part-way. Use the maintained fork under the same module name (`"react-native-fs": "npm:@dr.pogodin/react-native-fs@~2.40.4"`; it needs the New Architecture and React Native 0.73+, and has no default export), patch `RNFSManager.reject`, or set `mediaCaching` to `OFF`.

## Caching modes

The caching mode is set once, in the `OfflineConfig` you pass at initialization, and is fixed for the SDK session. There is no runtime switch — to change it, re-initialize the SDK with a new config.

| `MediaCachingMode` | Behavior |
| - | - |
| `AUTO` (default) | Incoming media bytes are eagerly downloaded and cached on any network, then LRU-managed. The outgoing copy is retained after the server acknowledges the send. |
| `OFF` | Incoming media bytes are **not** cached. The message metadata (including the file URL) is still cached, so the message appears offline and the UI loads media live from the URL when online. The outgoing copy is dropped after the send is acknowledged. |

Sending media always works in both modes.

```typescript theme={null}
const offlineConfig = new CometChat.OfflineConfig({
  enabled: true,
  mediaCaching: CometChat.MediaCachingMode.AUTO,
  maxMediaCacheSize: 500 * 1024 * 1024,
});
```

Read the mode in effect at runtime with `CometChat.getMediaCachingMode()`. It returns `MediaCachingMode.OFF` whenever offline support is not active, whatever the app configured, so a UI layer can rely on it without reading the config itself.

## What downloads automatically

In `AUTO` mode, media bytes are downloaded and cached automatically on the two live receive paths:

* a **realtime** media message received while connected, and
* opening a chat **online** (the server read refreshes and caches its media).

A media message that arrives through a reconnect catch-up [sync](https://www.cometchat.com/docs/sdk/react-native/offline-sync.md) is cached as **metadata only** and renders as a placeholder; its bytes download lazily the next time you open that chat online. This holds in both `AUTO` and `OFF`.

In `OFF` mode, nothing is auto-downloaded on any path; media loads live from its URL when online.

## Reading cached media

When bytes are already cached, the SDK **hydrates the local path automatically** on every message it delivers — you do not have to ask. Read it straight off the attachment:

```typescript theme={null}
const attachment = mediaMessage.getAttachment();
const localPath = attachment.getLocalPath(); // defined when bytes are cached
if (localPath) {
  // Render directly from disk — no network fetch.
}
```

To look up the cached path explicitly, call `getCachedMediaPath`. It never downloads, and resolves `null` when the bytes are not cached (call `downloadMedia` to fetch them). It accepts a `MediaMessage` or a bare `Attachment`.

```typescript theme={null}
const path: string | null = await CometChat.getCachedMediaPath(attachment);
```

> [!NOTE]
>
> Each attachment of a multi-attachment message resolves independently. An attachment with no cached bytes has no local path and the UI loads it from the URL as before.

## Downloading on demand

Call `downloadMedia` to fetch and cache an attachment's bytes — for example when the user taps a placeholder. The SDK resolves cached bytes first, then a valid URL, then, for a CometChat-hosted URL the server rejects as expired or forbidden (HTTP 401, 403 or 410), one re-signed URL. Any other download failure is not retried with a re-signed URL. The Promise resolves with the local path, and also sets it on the attachment. It resolves `null` if the bytes could not be obtained, or if offline support is off.

> [tab] TypeScript
>
> ```typescript theme={null}
> const path: string | null = await CometChat.downloadMedia(attachment, {
>   onMediaDownloaded: (messageId: string, attachmentKey: string, localPath: string) => {
>     // Bytes cached; render from localPath.
>   },
>   onMediaProgress: (messageId: string, attachmentKey: string, percent: number) => {
>     // 0..100, best-effort.
>   },
>   onMediaDownloadFailed: (messageId: string, attachmentKey: string, error: { code: string; message: string }) => {
>     console.log(error.code); // MEDIA_OFFLINE_NOT_CACHED | MEDIA_DOWNLOAD_FAILED | ERROR_OFFLINE_DISABLED
>   },
> });
> ```

> [tab] JavaScript
>
> ```javascript theme={null}
> const path = await CometChat.downloadMedia(attachment, {
>   onMediaDownloaded: (messageId, attachmentKey, localPath) => {
>     // Bytes cached; render from localPath.
>   },
>   onMediaProgress: (messageId, attachmentKey, percent) => {
>     // 0..100, best-effort.
>   },
>   onMediaDownloadFailed: (messageId, attachmentKey, error) => {
>     console.log(error.code); // MEDIA_OFFLINE_NOT_CACHED | MEDIA_DOWNLOAD_FAILED | ERROR_OFFLINE_DISABLED
>   },
> });
> ```

`onMediaProgress(…, percent)` reports `0..100`, best-effort. It is emitted only when the server reports a content length; for an unknown-length download it is skipped and the call goes straight to `onMediaDownloaded`. Progress never blocks or fails the download. All three callbacks are optional.

The failure callback receives one of these codes:

| Error code | Meaning |
| - | - |
| `MEDIA_OFFLINE_NOT_CACHED` | The bytes are not cached and could not be fetched, for example because the device is offline, the attachment has no usable URL, or the download did not succeed. |
| `MEDIA_DOWNLOAD_FAILED` | The download raised an unexpected error. |
| `ERROR_OFFLINE_DISABLED` | Offline support is not active. |

The listener also accepts the shorter shape `{ onSuccess(localPath), onProgress(percent), onError(error) }`.

## Observe background downloads

In `AUTO` mode, incoming media downloads in the background with no per-call callback. Register a media download listener to learn when the bytes land (or fail) so a UI tile can refresh.

> [tab] TypeScript
>
> ```typescript theme={null}
> await CometChat.addMediaDownloadListener("MEDIA_LISTENER", {
>   onMediaDownloaded: (messageId: string, attachmentKey: string, localPath: string) => {
>     // messageId:     the message's server id (or local id if not yet sent)
>     // attachmentKey: the attachment URL without its query string
>     // localPath:     where the bytes landed
>   },
>   onMediaDownloadFailed: (messageId: string, attachmentKey: string, error: { code: string; message: string }) => {},
> });
> ```

> [tab] JavaScript
>
> ```javascript theme={null}
> await CometChat.addMediaDownloadListener("MEDIA_LISTENER", {
>   onMediaDownloaded: (messageId, attachmentKey, localPath) => {
>     // messageId:     the message's server id (or local id if not yet sent)
>     // attachmentKey: the attachment URL without its query string
>     // localPath:     where the bytes landed
>   },
>   onMediaDownloadFailed: (messageId, attachmentKey, error) => {},
> });
> ```

Remove the listener with `CometChat.removeMediaDownloadListener("MEDIA_LISTENER")`. The background listener carries no progress callback — progress is reported only on the explicit `downloadMedia` path above.

## Media cache size limit

Cached media bytes are bounded by `maxMediaCacheSize` (default **500 MB**; `0` means unlimited). When the cache exceeds the limit, the least recently accessed media is deleted first until it is back under budget. Media for un-sent (queued) messages and in-progress uploads is never evicted and does not count toward the budget. Bulk list loads and syncs do not refresh an item's recency.

Media is stored as files in an app-private, per-user directory; the location is not configurable. Eviction runs silently in the background — there is no cache-limit callback. To reason about size, use `CometChat.getCacheSize()` and the configured limits. See the [configuration reference](https://www.cometchat.com/docs/sdk/react-native/offline-reference.md) for the full set of options.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.