---
source_url: https://www.pubnub.com/docs/sdks/c/api-reference/files
title: File Sharing API for C SDK
updated_at: 2026-09-24T12:25:41.000Z
sdk_name: PubNub C SDK
sdk_version: 1.0.0
---

# File Sharing API for C SDK

PubNub C SDK, use the latest version: 1.0.0

## Documentation index

To discover more PubNub resources:

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

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

File sharing lets you upload a file to a channel and notify subscribers that it is available, without building your own storage integration. Every function on this page is declared in `#include <pubnub/features/files.h>` and gated by `PUBNUB_ENABLE_FILES`.

Like every other feature entry point in this SDK, each function that performs network I/O returns a `pubnub_future_t` by value. Consume it with any of the three styles described in [Calling patterns](https://www.pubnub.com/docs/sdks/c/environment-setup.md#calling-patterns): cooperative polling, blocking `pubnub_await`, or callback via `pubnub_async`. `pubnub_get_file_url()` is the one exception: it does no network I/O and returns a plain `pubnub_res_t` directly.

`pubnub_send_file()` deserves a closer look before you use it: it is not one HTTP request, but three, chained behind a single future and a single request-pool slot. [Send file](#send-file) below explains what that means for error handling, and in particular what to do when the upload succeeds but the notification does not.

:::note Two feature flags, two different scopes
`PUBNUB_ENABLE_FILES` gates every function and struct on this page except the subscribe-side file event described in [Receive file share events](#receive-file-share-events), which is gated by `PUBNUB_ENABLE_SUBSCRIBE` instead. A build with subscribe enabled and Files disabled still compiles that extractor.
`PUBNUB_ENABLE_FILESYSTEM` gates the platform provider's `file_load` member identically on POSIX, Windows, and FreeRTOS: when it's `OFF`, `file_load` is `NULL` on all three, and `file_path` support in `pubnub_send_file_opts_t` returns `PUBNUB_ERR_NOT_SUPPORTED`; when it's `ON`, `file_load` is wired on all three. There is no asymmetry among those three. The `full` profile turns this flag `ON`; `minimal` and `embedded` turn it `OFF` (moot for `minimal`/`embedded` in practice, since both also disable `PUBNUB_ENABLE_FILES` itself). The Zephyr platform provider is the one exception: it sets `file_load = NULL` unconditionally, regardless of `PUBNUB_ENABLE_FILESYSTEM`, so `file_path` support never compiles in on Zephyr today. Sending from an in-memory buffer (`data`/`data_len`, with `file_path` left `NULL`) is unaffected by this flag on every platform, including Zephyr. It's the portable choice on a filesystem-less target.
:::

## Send file

Upload the file to a specified channel.

This method covers the entire process of sending a file, including preparation, uploading the file to a cloud storage service, and post-uploading messaging on a channel.

For the last messaging step, `pubnub_send_file` internally calls the [pubnub_publish_file_message](#publish-file-message) method to publish a message on the channel.

The published message contains metadata about the file, such as the file identifier and name, enabling others on the channel to find out about the file and access it.

### Method(s)

```c
pubnub_future_t pubnub_send_file(pubnub_context_t* ctx, const pubnub_send_file_opts_t* opts);
```

| Parameter | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| channel | const | Yes | `—` | Borrowed, NUL-terminated. |
| file_name | const | Optional | `—` | Borrowed, NUL-terminated. The server may assign a different final name. |
| data | const | Optional | `—` | Borrowed. Must remain valid until `pubnub_future_is_ready()` returns non-zero. |
| data_len | size_t | Optional | `—` | Byte count of `data`. |
| content_type | const | Optional | `NULL → "application/octet-stream"` | Borrowed, NUL-terminated. |
| message | const | Optional | `NULL` | Borrowed, NUL-terminated. Must be valid JSON. Encrypted separately from the file content itself when a `crypto_module` is configured. See [Encryption](https://www.pubnub.com/docs/sdks/c/api-reference/encryption.md). |
| meta | const | Optional | `NULL` | Borrowed, NUL-terminated. |
| custom_message_type | const | Optional | `NULL` | Borrowed, NUL-terminated, 3-50 characters. |
| store | int | Optional | `1 (store)` | Whether to store the published file message in history. |
| ttl | unsigned | Optional | `0 (account default)` | Minutes to retain the file message. |
| upload_timeout_ms | uint32_t | Optional | `0 → 300000 ms` | Timeout for the storage-upload step only. |
| timeout_ms | uint32_t | Optional | `0 → pubnub_config_t.transaction_timeout_ms` | Applies to the generate-upload-url and publish steps; not to the storage upload. |
| file_path | const | Optional | `NULL` | Borrowed, NUL-terminated. Read via the platform provider's `file_load`; see the feature-flag note above. |

Exactly one of `file_name` and `file_path` must be set. If `file_path` is set and `file_name` is not, the SDK derives `file_name` from the path's basename. Supplying `data` with a non-zero `data_len` but no `data` pointer and no `file_path` is `PUBNUB_ERR_INVALID_ARGUMENT`.

**C-family contract**

* **Header** — `#include <pubnub/features/files.h>`
* **Types** — `pubnub_send_file_opts_t`, `pubnub_send_file_result_t`
* **Prerequisite** — an initialized context with `publish_key` and `subscribe_key` set
* **Feature flag** — `PUBNUB_ENABLE_FILES`
* **Ownership / lifetime** — `data` is borrowed and must stay valid until the future is ready; `id`/`name` in the result alias memory owned by the future and are valid only until `pubnub_future_release`
* **Buffers** — no client-side file-size limit; the server enforces a maximum (the header's own comment says "typically 5 MB," not a guaranteed number) and classifies an oversized upload as `PUBNUB_ERR_INVALID_ARGUMENT`
* **Blocking** — never blocks; returns a `pubnub_future_t` immediately, though the operation it starts runs up to three sequential HTTP requests before that future becomes ready

:::note One future, three HTTP requests, one request-pool slot
`pubnub_send_file()` does not return once the file reaches storage. It drives all three steps itself, internally, and only completes the future at the end:
1. **Generate an upload URL.** A request to PubNub for a presigned storage URL and a server-assigned file `id`.
2. **Upload to storage.** The file content, encrypted first if a `crypto_module` is configured on the context, is uploaded directly to the presigned URL.
3. **Publish the file message.** A message announcing `id` and `name` (plus your optional `message`/`meta`) is published to `channel`. This is the same thing [Publish file message](#publish-file-message) does on its own.
All three steps reuse the same request-pool slot. You never see or drive them individually, and `pubnub_process()` (or `pubnub_await`, or the async callback) is all you call regardless of which step is in flight. What matters operationally is what happens when step 2 or step 3 fails on its own, covered next.
:::

:::warning A failed publish step still means the file exists. Do not call pubnub_send_file() again.
If step 2 (the storage upload) fails, that failure is terminal for this attempt. The presigned URL from step 1 is not reusable, there is no automatic retry, and the future completes with an error. `pubnub_send_file_result()` returns a zero-initialized result. Call `pubnub_send_file()` again from scratch. Nothing was created server-side.
If step 2 succeeds but step 3 (the publish) fails, the outcome is different: this is the case that's easy to get wrong. The future still completes with an error, but `pubnub_send_file_result()` returns a non-`NULL` `id` and `name`. The file is already stored. Only the announcement failed.
Calling `pubnub_send_file()` again in that case re-uploads the same content under a brand new `id`, leaving the original file orphaned in storage. Instead, read `id`/`name` from the failed result and call [pubnub_publish_file_message()](#publish-file-message) with them to finish the job without re-uploading. See [Recover from a publish failure without re-uploading](#recover-from-a-publish-failure-without-re-uploading) below.
:::

### Sample code

```c
#include <pubnub/client.h>
#include <pubnub/features/files.h>
#include <pubnub/future.h>
#include <pubnub/response.h>

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void)
{
    pubnub_config_t cfg = pubnub_config_defaults();
    cfg.publish_key   = "demo";
    cfg.subscribe_key = "demo";
    cfg.user_id       = "my_unique_user_id";

    pubnub_context_t* ctx = pubnub_create(&cfg);
    if (NULL == ctx) {
        return EXIT_FAILURE;
    }

    static const char content[] = "Hello PubNub Files!";

    pubnub_send_file_opts_t opts = PUBNUB_SEND_FILE_OPTS_INIT;
    opts.channel      = "my_channel";
    opts.file_name    = "greeting.txt";
    opts.data         = (const uint8_t*)content;
    opts.data_len     = strlen(content);
    opts.content_type = "text/plain";

    pubnub_future_t future = pubnub_send_file(ctx, &opts);
    while (!pubnub_future_is_ready(future)) {
        pubnub_process(ctx);
    }

    if (PUBNUB_OK == pubnub_future_status(future)) {
        pubnub_send_file_result_t r = pubnub_send_file_result(future);
        printf("id: %.*s, name: %.*s, timetoken: %.*s\n",
               (int)r.id.len, r.id.ptr,
               (int)r.name.len, r.name.ptr,
               (int)r.timetoken.len, r.timetoken.ptr);
    }
    else {
        pubnub_string_view_t err = pubnub_response_error_message(future);
        printf("send_file failed: %.*s\n", (int)err.len, err.ptr);
    }

    pubnub_future_release(future);
    pubnub_destroy(ctx);
    return EXIT_SUCCESS;
}
```

Adapted from `examples/files/send_file.c` (cooperative polling). This example takes the happy path only. See [Recover from a publish failure without re-uploading](#recover-from-a-publish-failure-without-re-uploading) for the partial-failure case.

### Returns

```c
pubnub_send_file_result_t pubnub_send_file_result(pubnub_future_t future);
```

`pubnub_send_file_result_t` has 3 fields:

| Field | Type | Description |
| --- | --- | --- |
| `id` | `pubnub_string_view_t` | Server-assigned file identifier. Populated on complete success and on the publish-failed partial case described above; zero-initialized (`id.ptr == NULL`) for every earlier failure. |
| `name` | `pubnub_string_view_t` | Stored file name. Follows the same populated/zero rule as `id`. |
| `timetoken` | `pubnub_timetoken_t` | Publish timetoken. Valid only when all three steps succeed. |

Every view aliases memory owned by the future and is valid only until `pubnub_future_release`.

Use `id.ptr` together with `pubnub_future_status()` to tell the two failure cases apart:

| `pubnub_future_status()` | `id.ptr` | What happened | Recovery |
| --- | --- | --- | --- |
| `PUBNUB_OK` | non-`NULL` | All three steps succeeded. | — |
| error | `NULL` | Failed generating the upload URL, or the storage upload itself failed. Nothing was created server-side, or the terminal upload failure discarded what was created. | Retry `pubnub_send_file()` from scratch. |
| error | non-`NULL` | Storage upload succeeded; publishing the file message failed. | Call `pubnub_publish_file_message()` with `id`/`name`. Do not call `pubnub_send_file()` again. |

### Other examples

#### Recover from a publish failure without re-uploading

This combines the two functions from the table above into one recovery path. Try the composed send. If it fails with `id`/`name` still populated, finish the job with `pubnub_publish_file_message()` instead of re-sending the file.

```c
#include <pubnub/client.h>
#include <pubnub/features/files.h>
#include <pubnub/future.h>
#include <pubnub/response.h>

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static int publish_recovered_file_message(pubnub_context_t* ctx,
                                           const char*        channel,
                                           const char*        file_id,
                                           const char*        file_name)
{
    pubnub_publish_file_message_opts_t opts = PUBNUB_PUBLISH_FILE_MESSAGE_OPTS_INIT;
    opts.channel   = channel;
    opts.file_id   = file_id;
    opts.file_name = file_name;

    pubnub_future_t future = pubnub_publish_file_message(ctx, &opts);
    while (!pubnub_future_is_ready(future)) {
        pubnub_process(ctx);
    }

    const pubnub_res_t status = pubnub_future_status(future);
    if (PUBNUB_OK == status) {
        pubnub_publish_file_message_result_t r = pubnub_publish_file_message_result(future);
        printf("recovered: published at %.*s\n", (int)r.timetoken.len, r.timetoken.ptr);
    }
    else {
        printf("recovery publish also failed; retry pubnub_publish_file_message later\n");
    }

    pubnub_future_release(future);
    return PUBNUB_OK == status ? 0 : 1;
}

int main(void)
{
    pubnub_config_t cfg = pubnub_config_defaults();
    cfg.publish_key   = "demo";
    cfg.subscribe_key = "demo";
    cfg.user_id       = "my_unique_user_id";

    pubnub_context_t* ctx = pubnub_create(&cfg);
    if (NULL == ctx) {
        return EXIT_FAILURE;
    }

    static const char content[]   = "Hello PubNub Files!";
    static const char channel[]   = "my_channel";

    pubnub_send_file_opts_t opts = PUBNUB_SEND_FILE_OPTS_INIT;
    opts.channel      = channel;
    opts.file_name    = "greeting.txt";
    opts.data         = (const uint8_t*)content;
    opts.data_len     = strlen(content);
    opts.content_type = "text/plain";

    pubnub_future_t future = pubnub_send_file(ctx, &opts);
    while (!pubnub_future_is_ready(future)) {
        pubnub_process(ctx);
    }

    const pubnub_res_t     status = pubnub_future_status(future);
    pubnub_send_file_result_t result = pubnub_send_file_result(future);

    int rc = 0;
    if (PUBNUB_OK == status) {
        printf("sent: id=%.*s name=%.*s\n",
               (int)result.id.len, result.id.ptr,
               (int)result.name.len, result.name.ptr);
        pubnub_future_release(future);
    }
    else if (NULL != result.id.ptr) {
        /* Upload succeeded, only the publish step failed. Copy id/name out
           before releasing the future -- the views alias data owned by it. */
        char id_buf[64];
        char name_buf[256];
        size_t id_len   = result.id.len   < sizeof(id_buf) - 1   ? result.id.len   : sizeof(id_buf) - 1;
        size_t name_len = result.name.len < sizeof(name_buf) - 1 ? result.name.len : sizeof(name_buf) - 1;
        memcpy(id_buf, result.id.ptr, id_len);
        id_buf[id_len] = '\0';
        memcpy(name_buf, result.name.ptr, name_len);
        name_buf[name_len] = '\0';

        pubnub_future_release(future);

        /* Do NOT call pubnub_send_file() again here -- that would upload
           the content a second time. Finish delivery from what already
           exists in storage instead. */
        rc = publish_recovered_file_message(ctx, channel, id_buf, name_buf);
    }
    else {
        /* id.ptr == NULL: nothing was created. Safe to retry pubnub_send_file() from scratch. */
        printf("send_file failed before upload completed; retry pubnub_send_file() from scratch\n");
        pubnub_future_release(future);
        rc = 1;
    }

    pubnub_destroy(ctx);
    return rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}
```

This combined flow builds on the `pubnub_send_file`/`pubnub_publish_file_message` signatures and result structs described above.

#### Send file asynchronously

```c
#include <pubnub/client.h>
#include <pubnub/features/files.h>
#include <pubnub/future.h>
#include <pubnub/response.h>

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static volatile int s_done;

static void on_send_complete(pubnub_future_t future, pubnub_res_t status, void* user_data)
{
    (void)user_data;

    if (PUBNUB_OK == status) {
        pubnub_send_file_result_t r = pubnub_send_file_result(future);
        printf("sent: id=%.*s name=%.*s\n",
               (int)r.id.len, r.id.ptr,
               (int)r.name.len, r.name.ptr);
    }
    else {
        pubnub_string_view_t err = pubnub_response_error_message(future);
        printf("send_file failed: %.*s\n", (int)err.len, err.ptr);
    }

    pubnub_future_release(future);
    s_done = 1;
}

int main(void)
{
    pubnub_config_t cfg = pubnub_config_defaults();
    cfg.publish_key   = "demo";
    cfg.subscribe_key = "demo";
    cfg.user_id       = "my_unique_user_id";

    pubnub_context_t* ctx = pubnub_create(&cfg);
    if (NULL == ctx) {
        return EXIT_FAILURE;
    }

    static const char content[] = "Hello PubNub Files (async)!";

    pubnub_send_file_opts_t opts = PUBNUB_SEND_FILE_OPTS_INIT;
    opts.channel      = "my_channel";
    opts.file_name    = "greeting_async.txt";
    opts.data         = (const uint8_t*)content;
    opts.data_len     = strlen(content);
    opts.content_type = "text/plain";

    pubnub_future_t future = pubnub_send_file(ctx, &opts);

    pubnub_res_t rc = pubnub_async(future, on_send_complete, NULL);
    if (PUBNUB_OK != rc) {
        pubnub_future_release(future);
        pubnub_destroy(ctx);
        return EXIT_FAILURE;
    }

    /* On threaded platforms a background thread drives I/O and calls
       on_send_complete when done, so pubnub_process() here is a no-op.
       On embedded targets without threads, this same call is what
       actually drives the upload to completion. */
    while (!s_done) {
        pubnub_process(ctx);
    }

    pubnub_destroy(ctx);
    return EXIT_SUCCESS;
}
```

Adapted from `examples/files/send_file_async.c` (callback-driven, via `pubnub_async`). The example's own comment notes that list, download, delete, and publish-file-message support the same async pattern.

### Error responses

`pubnub_res_t` is the universal status type for this SDK; see [The pubnub_res_t result catalog](https://www.pubnub.com/docs/sdks/c/status-events.md#the-pubnub_res_t-result-catalog) for the full list. Files-specific behavior worth calling out:

* Storage (S3) returns a flat XML error body on upload failure, and the SDK classifies it. `EntityTooLarge` becomes `PUBNUB_ERR_INVALID_ARGUMENT`: the file exceeds the server-configured size limit, and there is no client-side pre-check. An `AccessDenied` response whose message contains "expired" becomes `PUBNUB_ERR_TIMEOUT`: the presigned upload URL from step 1 expired before the upload ran. Any other storage failure becomes `PUBNUB_ERR_TRANSPORT`.
* A missing `publish_key` in `pubnub_config_t` fails both `pubnub_send_file()` and `pubnub_publish_file_message()` immediately with `PUBNUB_ERR_INVALID_ARGUMENT`.
* Files responses populate the `code` sub-field of `pubnub_service_error_t` with a Files-specific numeric sub-code that no other feature's error responses carry today. See [Retrieving server error detail](https://www.pubnub.com/docs/sdks/c/status-events.md#retrieving-server-error-detail).

## List channel files

Retrieve a page of files previously uploaded to a channel.

### Method(s)

```c
pubnub_future_t pubnub_list_files(pubnub_context_t* ctx, const pubnub_list_files_opts_t* opts);
```

| Parameter | Description |
| --- | --- |
| `channel` *Type: `const char*`Default: — | Borrowed, NUL-terminated. |
| `limit`Type: `int`Default: `0` → server default of 100 | Valid range 1-100. |
| `next`Type: `const char*`Default: `NULL` (first page) | Borrowed. Pass the previous response's `pubnub_list_files_result_t.next` to fetch the next page. |
| `timeout_ms`Type: `uint32_t`Default: `0` → `pubnub_config_t.transaction_timeout_ms` | — |

**C-family contract**

* **Header** — `#include <pubnub/features/files.h>`
* **Types** — `pubnub_list_files_opts_t`, `pubnub_list_files_result_t`, `pubnub_file_info_t`
* **Prerequisite** — an initialized context with `subscribe_key` set
* **Feature flag** — `PUBNUB_ENABLE_FILES`
* **Ownership / lifetime** — every view in the result and in each `pubnub_file_info_t` aliases memory owned by the future and is valid only until `pubnub_future_release`
* **Blocking** — never blocks; returns a `pubnub_future_t` immediately

### Sample code

```c
#include <pubnub/client.h>
#include <pubnub/features/files.h>
#include <pubnub/future.h>
#include <pubnub/response.h>

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    pubnub_config_t cfg = pubnub_config_defaults();
    cfg.subscribe_key = "demo";
    cfg.user_id       = "my_unique_user_id";

    pubnub_context_t* ctx = pubnub_create(&cfg);
    if (NULL == ctx) {
        return EXIT_FAILURE;
    }

    pubnub_list_files_opts_t opts = PUBNUB_LIST_FILES_OPTS_INIT;
    opts.channel = "my_channel";
    opts.limit   = 10;

    pubnub_future_t future = pubnub_list_files(ctx, &opts);
    while (!pubnub_future_is_ready(future)) {
        pubnub_process(ctx);
    }

    if (PUBNUB_OK == pubnub_future_status(future)) {
        pubnub_list_files_result_t r = pubnub_list_files_result(future);
        printf("files on channel (%u):\n", r.count);
        for (uint32_t i = 0; i < r.count; ++i) {
            pubnub_file_info_t f = pubnub_list_files_result_file_at(future, i);
            printf("  %.*s  id=%.*s  %u bytes\n",
                   (int)f.name.len, f.name.ptr,
                   (int)f.id.len, f.id.ptr,
                   f.size);
        }
    }
    else {
        pubnub_string_view_t err = pubnub_response_error_message(future);
        printf("list_files failed: %.*s\n", (int)err.len, err.ptr);
    }

    pubnub_future_release(future);
    pubnub_destroy(ctx);
    return EXIT_SUCCESS;
}
```

Adapted from `examples/files/list_files.c` (cooperative polling).

### Returns

```c
pubnub_list_files_result_t pubnub_list_files_result(pubnub_future_t future);
pubnub_file_info_t         pubnub_list_files_result_file_at(pubnub_future_t future, size_t index);
```

`pubnub_list_files_result_t` has 2 fields:

| Field | Type | Description |
| --- | --- | --- |
| `count` | `uint32_t` | Number of files in this page, the loop bound for `pubnub_list_files_result_file_at`. |
| `next` | `pubnub_string_view_t` | Pagination token. Empty (`.len == 0`) on the last page. |

`pubnub_file_info_t` has 4 fields:

| Field | Type | Description |
| --- | --- | --- |
| `id` | `pubnub_string_view_t` | File identifier. |
| `name` | `pubnub_string_view_t` | File name. |
| `size` | `uint32_t` | Size in bytes. |
| `created` | `pubnub_string_view_t` | ISO 8601 creation timestamp. |

`pubnub_list_files_result_file_at()` returns a zero-initialized `pubnub_file_info_t` for `index >= count` or when the future is not yet ready. It never reads out of bounds. Every view above aliases memory owned by the future and is valid only until `pubnub_future_release`.

## Get file URL

Build a URL that points at a previously uploaded file, without making a network call.

### Method(s)

```c
pubnub_res_t pubnub_get_file_url(pubnub_context_t*                 ctx,
                                 const pubnub_get_file_url_opts_t* opts,
                                 char*                             buf,
                                 size_t                            buf_size,
                                 size_t*                           out_len);
```

| Parameter | Description |
| --- | --- |
| `channel` *Type: `const char*`Default: — | Borrowed, NUL-terminated. |
| `file_id` *Type: `const char*`Default: — | Borrowed, NUL-terminated. |
| `file_name` *Type: `const char*`Default: — | Borrowed, NUL-terminated. |

There is no `timeout_ms` field on `pubnub_get_file_url_opts_t`. The call makes no network request.

**C-family contract**

* **Header** — `#include <pubnub/features/files.h>`
* **Types** — `pubnub_get_file_url_opts_t`
* **Prerequisite** — an initialized context; the URL is built from the context's configured `origin` and `subscribe_key`
* **Feature flag** — `PUBNUB_ENABLE_FILES`
* **Ownership / lifetime** — `buf` is a caller-owned buffer; `out_len` receives the number of bytes the URL needs (excluding the NUL terminator) regardless of whether `buf` was large enough, so you can probe the required size even when the call returns `PUBNUB_ERR_BUFFER_TOO_SMALL`
* **Blocking** — synchronous, local computation only; returns a `pubnub_res_t` directly, not a `pubnub_future_t`

:::warning This URL has no signature, no expiry, and no crypto awareness
`pubnub_get_file_url()` makes no network request. It only formats `https://{origin}/v1/files/{subscribe_key}/channels/{channel}/files/{id}/{name}` into your buffer from the context's configured `origin` and `subscribe_key`, plus the three identifiers you pass in. Nothing about the result is signed or time-limited, and the function has no awareness of whether the target file was encrypted at upload time. If the file was uploaded with a `crypto_module` configured, this URL still points at the encrypted bytes in storage. Only `pubnub_download_file()` performs decryption. Anyone who fetches this URL directly, including a browser, receives exactly what is stored: ciphertext if the sender encrypted the file, plaintext otherwise. Do not hand this URL to an untrusted party for an encrypted file expecting it to serve plaintext.
:::

### Sample code

```c
#include <pubnub/client.h>
#include <pubnub/features/files.h>

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    pubnub_config_t cfg = pubnub_config_defaults();
    cfg.subscribe_key = "demo";
    cfg.user_id       = "my_unique_user_id";

    pubnub_context_t* ctx = pubnub_create(&cfg);
    if (NULL == ctx) {
        return EXIT_FAILURE;
    }

    pubnub_get_file_url_opts_t opts = PUBNUB_GET_FILE_URL_OPTS_INIT;
    opts.channel   = "my_channel";
    opts.file_id   = "abc-123";
    opts.file_name = "report.pdf";

    char   url_buf[512];
    size_t url_len = 0;

    pubnub_res_t rc = pubnub_get_file_url(ctx, &opts, url_buf, sizeof(url_buf), &url_len);
    if (PUBNUB_OK == rc) {
        printf("URL: %.*s\n", (int)url_len, url_buf);
    }
    else if (PUBNUB_ERR_BUFFER_TOO_SMALL == rc) {
        printf("buffer too small; need %zu bytes\n", url_len);
    }

    pubnub_destroy(ctx);
    return EXIT_SUCCESS;
}
```

Adapted from the header's own `@code` example (`files.h:789-800`).

### Returns

`pubnub_get_file_url()` returns a `pubnub_res_t` directly:

| Value | Meaning |
| --- | --- |
| `PUBNUB_OK` | `buf` now holds the URL; `out_len` (if non-`NULL`) holds its length. |
| `PUBNUB_ERR_BUFFER_TOO_SMALL` | `buf` was too small. `out_len` still holds the length actually needed. Resize and retry. |
| `PUBNUB_ERR_INVALID_ARGUMENT` | `ctx`, `opts->channel`, or `buf` was `NULL`. |

See [The pubnub_res_t result catalog](https://www.pubnub.com/docs/sdks/c/status-events.md#the-pubnub_res_t-result-catalog) for the full set of values this SDK can return.

## Download file

### Method(s)

```c
pubnub_future_t pubnub_download_file(pubnub_context_t* ctx, const pubnub_download_file_opts_t* opts);
```

| Parameter | Description |
| --- | --- |
| `channel` *Type: `const char*`Default: — | Borrowed, NUL-terminated. |
| `file_id` *Type: `const char*`Default: — | Borrowed, NUL-terminated. |
| `file_name` *Type: `const char*`Default: — | Borrowed, NUL-terminated. |
| `timeout_ms`Type: `uint32_t`Default: `0` → `pubnub_config_t.transaction_timeout_ms` | — |

**C-family contract**

* **Header** — `#include <pubnub/features/files.h>`
* **Types** — `pubnub_download_file_opts_t`, `pubnub_download_file_result_t`
* **Prerequisite** — an initialized context with `subscribe_key` set
* **Feature flag** — `PUBNUB_ENABLE_FILES`
* **Ownership / lifetime** — `data` in the result is borrowed and valid only until `pubnub_future_release`; there is no streaming API, so the entire file is buffered in memory until then
* **Buffers** — `PUBNUB_CFG_FILES_MAX_DOWNLOAD_SIZE` (default `0`, unlimited) caps the accepted response body; exceeding it fails the future with `PUBNUB_ERR_BUFFER_TOO_SMALL` before any bytes reach you
* **Blocking** — never blocks; returns a `pubnub_future_t` immediately

:::warning A silent decryption failure returns ciphertext, not an error
`pubnub_download_file_result_t.decrypted` is `1` only when a `crypto_module` is configured on the context and decryption succeeds. In that case, `data`/`data_len` are the plaintext bytes. Otherwise, whether no crypto module is configured or decryption fails for any reason, the function does not fail the future and raises no error. It silently falls back to returning `data`/`data_len` as the raw, un-decrypted response body, with `decrypted` set to `0`. A caller that writes `data` to disk without checking `decrypted` first can save ciphertext to disk while believing it has the original file. Nothing signals that anything went wrong.
:::

### Sample code

```c
#include <pubnub/client.h>
#include <pubnub/features/files.h>
#include <pubnub/future.h>
#include <pubnub/response.h>

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    pubnub_config_t cfg = pubnub_config_defaults();
    cfg.subscribe_key = "demo";
    cfg.user_id       = "my_unique_user_id";

    pubnub_context_t* ctx = pubnub_create(&cfg);
    if (NULL == ctx) {
        return EXIT_FAILURE;
    }

    pubnub_download_file_opts_t opts = PUBNUB_DOWNLOAD_FILE_OPTS_INIT;
    opts.channel   = "my_channel";
    opts.file_id   = "abc-123";
    opts.file_name = "greeting.txt";

    pubnub_future_t future = pubnub_download_file(ctx, &opts);
    while (!pubnub_future_is_ready(future)) {
        pubnub_process(ctx);
    }

    if (PUBNUB_OK == pubnub_future_status(future)) {
        pubnub_download_file_result_t r = pubnub_download_file_result(future);
        if (r.decrypted) {
            printf("downloaded %zu decrypted bytes\n", r.data_len);
        }
        else {
            printf("downloaded %zu bytes, NOT decrypted -- "
                   "verify crypto_module configuration before trusting this content\n",
                   r.data_len);
        }
    }
    else {
        pubnub_string_view_t err = pubnub_response_error_message(future);
        printf("download failed: %.*s\n", (int)err.len, err.ptr);
    }

    pubnub_future_release(future);
    pubnub_destroy(ctx);
    return EXIT_SUCCESS;
}
```

Adapted from `examples/files/download_file.c` (cooperative polling), extended to check the `decrypted` flag described in the warning above. The shipped example prints the bytes without checking it.

### Returns

```c
pubnub_download_file_result_t pubnub_download_file_result(pubnub_future_t future);
```

`pubnub_download_file_result_t` has 3 fields:

| Field | Type | Description |
| --- | --- | --- |
| `data` | `const uint8_t*` | Borrowed. Valid until `pubnub_future_release`. |
| `data_len` | `size_t` | Byte count of `data`. |
| `decrypted` | `uint8_t` | `1` when a `crypto_module` is configured and decryption succeeded — `data`/`data_len` are plaintext. `0` in every other case: no crypto module configured, decryption not attempted, or decryption failed silently. When `0`, `data`/`data_len` are the raw (possibly still-encrypted) bytes from storage. See the warning above. |

## Delete file

### Method(s)

```c
pubnub_future_t pubnub_delete_file(pubnub_context_t* ctx, const pubnub_delete_file_opts_t* opts);
```

| Parameter | Description |
| --- | --- |
| `channel` *Type: `const char*`Default: — | Borrowed, NUL-terminated. |
| `file_id` *Type: `const char*`Default: — | Borrowed, NUL-terminated. |
| `file_name` *Type: `const char*`Default: — | Borrowed, NUL-terminated. |
| `timeout_ms`Type: `uint32_t`Default: `0` → `pubnub_config_t.transaction_timeout_ms` | — |

**C-family contract**

* **Header** — `#include <pubnub/features/files.h>`
* **Types** — `pubnub_delete_file_opts_t`. There is no feature-specific result struct.
* **Prerequisite** — an initialized context with `subscribe_key` set
* **Feature flag** — `PUBNUB_ENABLE_FILES`
* **Blocking** — never blocks; returns a `pubnub_future_t` immediately

### Sample code

```c
#include <pubnub/client.h>
#include <pubnub/features/files.h>
#include <pubnub/future.h>

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    pubnub_config_t cfg = pubnub_config_defaults();
    cfg.subscribe_key = "demo";
    cfg.user_id       = "my_unique_user_id";

    pubnub_context_t* ctx = pubnub_create(&cfg);
    if (NULL == ctx) {
        return EXIT_FAILURE;
    }

    pubnub_delete_file_opts_t opts = PUBNUB_DELETE_FILE_OPTS_INIT;
    opts.channel   = "my_channel";
    opts.file_id   = "abc-123";
    opts.file_name = "report.pdf";

    pubnub_future_t future = pubnub_delete_file(ctx, &opts);
    while (!pubnub_future_is_ready(future)) {
        pubnub_process(ctx);
    }

    if (PUBNUB_OK == pubnub_future_status(future)) {
        printf("file deleted\n");
    }

    pubnub_future_release(future);
    pubnub_destroy(ctx);
    return EXIT_SUCCESS;
}
```

Adapted from the header's own `@code` example (`files.h:647-663`).

### Returns

`pubnub_delete_file()` has no feature-specific result struct and no dedicated result-accessor function. Check `pubnub_future_status(future)` directly: `PUBNUB_OK` means the file was removed from storage. See [The pubnub_res_t result catalog](https://www.pubnub.com/docs/sdks/c/status-events.md#the-pubnub_res_t-result-catalog) for every other value it can return.

## Publish file message

Publish a message announcing a file that is already in storage, without uploading anything.

Call this directly when you already have a stored file's `id` and `name`. Most often that's from a failed `pubnub_send_file()`'s partial-success result (see [Send file](#send-file)), but it can also come from `pubnub_list_files_result_file_at()` for a file uploaded earlier. `pubnub_publish_file_message()` issues only the publish step. It never touches storage and never re-uploads content. Internally, `pubnub_send_file()` calls this same function for its own third step, so calling it directly for the recovery path exercises exactly the code that step already runs.

### Method(s)

```c
pubnub_future_t
pubnub_publish_file_message(pubnub_context_t*                         ctx,
                            const pubnub_publish_file_message_opts_t* opts);
```

| Parameter | Description |
| --- | --- |
| `channel` *Type: `const char*`Default: — | Borrowed, NUL-terminated. |
| `file_id` *Type: `const char*`Default: — | Borrowed, NUL-terminated. |
| `file_name` *Type: `const char*`Default: — | Borrowed, NUL-terminated. |
| `message`Type: `const char*`Default: `NULL` | Borrowed, NUL-terminated. Must be valid JSON. Encrypted before publish when a `crypto_module` is configured. |
| `meta`Type: `const char*`Default: `NULL` | Borrowed, NUL-terminated. |
| `custom_message_type`Type: `const char*`Default: `NULL` | Borrowed, NUL-terminated, 3-50 characters. |
| `store`Type: `int`Default: `1` (store) | Whether to store the file message in history. |
| `ttl`Type: `unsigned int`Default: `0` (account default) | Minutes to retain the file message. |
| `timeout_ms`Type: `uint32_t`Default: `0` → `pubnub_config_t.transaction_timeout_ms` | — |

**C-family contract**

* **Header** — `#include <pubnub/features/files.h>`
* **Types** — `pubnub_publish_file_message_opts_t`, `pubnub_publish_file_message_result_t`
* **Prerequisite** — an initialized context with `publish_key` and `subscribe_key` set
* **Feature flag** — `PUBNUB_ENABLE_FILES`
* **Ownership / lifetime** — `message`/`meta` are borrowed strings; the result's `timetoken` view is valid only until `pubnub_future_release`
* **Blocking** — never blocks; returns a `pubnub_future_t` immediately

### Sample code

```c
#include <pubnub/client.h>
#include <pubnub/features/files.h>
#include <pubnub/future.h>

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    pubnub_config_t cfg = pubnub_config_defaults();
    cfg.publish_key   = "demo";
    cfg.subscribe_key = "demo";
    cfg.user_id       = "my_unique_user_id";

    pubnub_context_t* ctx = pubnub_create(&cfg);
    if (NULL == ctx) {
        return EXIT_FAILURE;
    }

    pubnub_publish_file_message_opts_t opts = PUBNUB_PUBLISH_FILE_MESSAGE_OPTS_INIT;
    opts.channel   = "my_channel";
    opts.file_id   = "abc-123";
    opts.file_name = "report.pdf";
    opts.message   = "{\"caption\":\"Q4 report\"}";

    pubnub_future_t future = pubnub_publish_file_message(ctx, &opts);
    while (!pubnub_future_is_ready(future)) {
        pubnub_process(ctx);
    }

    if (PUBNUB_OK == pubnub_future_status(future)) {
        pubnub_publish_file_message_result_t r = pubnub_publish_file_message_result(future);
        printf("published at %.*s\n", (int)r.timetoken.len, r.timetoken.ptr);
    }

    pubnub_future_release(future);
    pubnub_destroy(ctx);
    return EXIT_SUCCESS;
}
```

Adapted from the header's own `@code` example (`files.h:737-758`).

### Returns

```c
pubnub_publish_file_message_result_t pubnub_publish_file_message_result(pubnub_future_t future);
```

`pubnub_publish_file_message_result_t` has a single field, `timetoken` (`pubnub_timetoken_t`): the publish timetoken, valid only after the future completes with `PUBNUB_OK`.

### Other examples

#### Trigger mobile push notifications for a file message

PubNub forwards a published message to APNs and/or FCM when its payload contains the reserved `pn_apns` and/or `pn_fcm` keys, the same mechanism used for any ordinary published message. Because `pubnub_publish_file_message()` ultimately publishes a JSON message like any other, include those keys in the `message` JSON alongside your own content to trigger a push notification for the file:

```c
#include <pubnub/client.h>
#include <pubnub/features/files.h>
#include <pubnub/future.h>

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    pubnub_config_t cfg = pubnub_config_defaults();
    cfg.publish_key   = "demo";
    cfg.subscribe_key = "demo";
    cfg.user_id       = "my_unique_user_id";

    pubnub_context_t* ctx = pubnub_create(&cfg);
    if (NULL == ctx) {
        return EXIT_FAILURE;
    }

    static const char message[] =
        "{\"file\":{\"description\":\"Q4 report\"},"
        "\"pn_apns\":{\"aps\":{\"alert\":\"New file: report.pdf\"}},"
        "\"pn_fcm\":{\"notification\":{\"title\":\"New file\",\"body\":\"report.pdf\"}}}";

    pubnub_publish_file_message_opts_t opts = PUBNUB_PUBLISH_FILE_MESSAGE_OPTS_INIT;
    opts.channel   = "my_channel";
    opts.file_id   = "abc-123";
    opts.file_name = "report.pdf";
    opts.message   = message;

    pubnub_future_t future = pubnub_publish_file_message(ctx, &opts);
    while (!pubnub_future_is_ready(future)) {
        pubnub_process(ctx);
    }

    if (PUBNUB_OK == pubnub_future_status(future)) {
        printf("published with push payload\n");
    }

    pubnub_future_release(future);
    pubnub_destroy(ctx);
    return EXIT_SUCCESS;
}
```

This sample follows the `pn_apns`/`pn_fcm` payload convention described in [Mobile Push](https://www.pubnub.com/docs/sdks/c/api-reference/mobile-push.md).

## Receive file share events

Subscribers to a channel receive a file-share event through the same subscribe stream as any other message, decoded through a dedicated typed extractor. The listener callback that receives it takes a `const pubnub_subscribe_event_t*`. See [The subscribe event struct](https://www.pubnub.com/docs/sdks/c/api-reference/publish-and-subscribe.md#the-subscribe-event-struct) for its fields and the full discriminant list.

### Method(s)

```c
pubnub_res_t pubnub_subscribe_event_file(pubnub_context_t*               ctx,
                                         const pubnub_subscribe_event_t* event,
                                         pubnub_subscribe_file_event_t*  out);
```

`pubnub_subscribe_file_event_t` has 7 fields:

| Field | Type | Description |
| --- | --- | --- |
| `channel` | `pubnub_string_view_t` | Channel the file was published on. |
| `subscription` | `pubnub_string_view_t` | Subscription match pattern. |
| `publisher` | `pubnub_string_view_t` | Publisher's `user_id`. |
| `file_id` | `pubnub_string_view_t` | Server-generated file identifier. |
| `file_name` | `pubnub_string_view_t` | File name. |
| `message` | `const pubnub_json_value_t*` | JSON-node pointer to the optional message attached at publish time. `NULL` when absent. Valid only inside the listener callback. |
| `timetoken` | `pubnub_string_view_t` | Publish timetoken. |

**C-family contract**

* **Header** — `#include <pubnub/features/subscribe_types.h>` (struct), `#include <pubnub/features/subscribe.h>` (extractor)
* **Types** — `pubnub_subscribe_file_event_t`, `pubnub_subscribe_event_t`
* **Prerequisite** — an active subscription delivering events to a listener; see [Listeners](https://www.pubnub.com/docs/sdks/c/api-reference/publish-and-subscribe.md#listeners)
* **Feature flag** — `PUBNUB_ENABLE_SUBSCRIBE`, not `PUBNUB_ENABLE_FILES`. This extractor compiles and works even in a build with Files disabled
* **Ownership / lifetime** — every view field aliases the subscribe event; `message` is a JSON-node pointer valid only for the duration of the listener callback
* **Blocking** — synchronous, local-only; not a `pubnub_future_t` operation

:::note Two similarly-named enums: use the subscribe one here
`pubnub_subscribe_event_file()` validates that `event->type` equals `PUBNUB_SUBSCRIBE_FILE`, a member of `pubnub_subscribe_message_type_t`. Do not confuse this with `PUBNUB_EVENT_TYPE_FILE`, a similarly-named member of History's own `pubnub_event_type_t` used on `pubnub_history_message_result_t` (see [The pubnub_event_type_t enum](https://www.pubnub.com/docs/sdks/c/api-reference/storage-and-playback.md#the-pubnub_event_type_t-enum)). The two enums are unrelated types on unrelated structs. Use `PUBNUB_SUBSCRIBE_FILE` here.
:::

### Sample code

```c
#include <pubnub/client.h>
#include <pubnub/features/subscribe.h>
#include <pubnub/features/subscribe_types.h>

#include <stdio.h>

static void on_message(const pubnub_subscribe_event_t* event, void* user_data)
{
    pubnub_context_t* ctx = (pubnub_context_t*)user_data;

    if (PUBNUB_SUBSCRIBE_FILE != event->type) {
        return;
    }

    pubnub_subscribe_file_event_t file;
    if (PUBNUB_OK == pubnub_subscribe_event_file(ctx, event, &file)) {
        printf("file_id: %.*s, file_name: %.*s\n",
               (int)file.file_id.len, file.file_id.ptr,
               (int)file.file_name.len, file.file_name.ptr);
    }
}
```

This callback registers like any other message listener. See [Listeners](https://www.pubnub.com/docs/sdks/c/api-reference/publish-and-subscribe.md#listeners) for how to attach it to a subscription and drive the event loop.

### Returns

`pubnub_subscribe_event_file()` returns `PUBNUB_OK` on success, `PUBNUB_ERR_INVALID_ARGUMENT` if any argument is `NULL` or `event->type` is not `PUBNUB_SUBSCRIBE_FILE`, and `PUBNUB_ERR_SERIALIZATION` if the event body fails to parse. On success, `out` is populated with the fields listed above.

## Terms in this document

* **Channel** - A pathway for sending and receiving messages between devices, created automatically when you first use it, that can handle any number of users and messages for different communication needs, like 1-1 text chats, group conversations, and other data streaming.
* **Channel (DataSync)** - A built-in entity class in DataSync that stores channel metadata as a JSON payload. The same channel used for messaging and Presence.
* **Channel pattern** - A way to group and analyze channel data to track performance metrics like message counts and user engagement over time with PubNub Insights.

Last updated at: 2026-09-24T12:25:41.000Z
