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

# Channel Groups 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.

[Channel groups](https://www.pubnub.com/docs/general/channels/subscribe.md#channel-groups) allow PubNub developers to bundle thousands of [channels](https://www.pubnub.com/docs/general/channels/overview.md) into a group that can be identified by a name. These channel groups can then be subscribed to, receiving data from the many back-end channels the channel group contains.

:::note Channel group operations
You can't publish to a channel group. You can only subscribe to it. To publish within the channel group, you need to publish to each channel individually.
:::

This page covers the four channel-group operations: add channels to a group, remove channels from a group, list the channels in a group, and delete a group entirely. All four are declared in `#include <pubnub/features/channel_groups.h>` and gated by `PUBNUB_ENABLE_CHANNEL_GROUPS`. This flag is `ON` in the `full` CMake profile but `OFF` in `minimal` and `embedded`. A build using either of those must turn it on explicitly with `-DPUBNUB_ENABLE_CHANNEL_GROUPS=ON`. See [Feature flags](https://www.pubnub.com/docs/sdks/c/environment-setup.md#feature-flags) for the full flag matrix.

To receive messages published on the channels inside a group, subscribe to the group as an entity with `pubnub_channel_group()`. See [Entities](https://www.pubnub.com/docs/sdks/c/api-reference/publish-and-subscribe.md#entities) in Publish & Subscribe. This page covers only the group's membership CRUD operations, not subscribing to it.

:::note Multiple channels are a single comma-separated string
Every function on this page that accepts more than one channel takes them as one comma-separated `const char*`, for example `"ch1,ch2,ch3"`, never an array. There is no array-based overload. Build the comma-separated string yourself before populating an options struct.
:::

## Add channels to a channel group

`pubnub_channel_group_add_channels()` adds one or more channels to a channel group. Creating a channel group happens implicitly the first time a channel is added to a group name that does not yet exist.

### Method(s)

```c
pubnub_future_t pubnub_channel_group_add_channels(pubnub_context_t* ctx, const pubnub_channel_group_add_opts_t* opts);
```

| Parameter | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| channel_group | const | Yes | `—` | Borrowed, NUL-terminated. Target channel group name. |
| channels | const | Yes | `—` | Borrowed, NUL-terminated. Comma-separated channel names to add, for example `"ch1,ch2,ch3"`. Not an array. |
| timeout_ms | uint32_t | Optional | `0 (inherits pubnub_config_t::transaction_timeout_ms)` | A non-zero value overrides the context-level timeout for this call only. |

Zero-initialize with `PUBNUB_CHANNEL_GROUP_ADD_OPTS_INIT` (expands to `{0}`) so every unspecified field takes its protocol-safe default.

**C-family contract**

* **Header** — `#include <pubnub/features/channel_groups.h>`
* **Types** — `pubnub_channel_group_add_opts_t`
* **Prerequisite** — an initialized context with `subscribe_key` and `user_id` set. Every shipped example configures only those two keys for channel-group calls; `publish_key` is not needed.
* **Feature flag** — `PUBNUB_ENABLE_CHANNEL_GROUPS`
* **Ownership / lifetime** — `channel_group` and `channels` are borrowed, NUL-terminated strings; the SDK does not copy or retain them past the call
* **Blocking** — never blocks; returns a `pubnub_future_t` immediately

### Sample code

```c
#include <pubnub/client.h>
#include <pubnub/features/channel_groups.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_channel_group_add_opts_t opts = PUBNUB_CHANNEL_GROUP_ADD_OPTS_INIT;
    opts.channel_group = "my_channel_group";
    opts.channels       = "ch1,ch2,ch3";

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

    pubnub_res_t result = pubnub_future_status(future);
    if (PUBNUB_OK == result) {
        printf("channels added to group\n");
    }
    else {
        printf("add channels failed: %d\n", (int)result);
    }

    pubnub_future_release(future);
    pubnub_destroy(ctx);
    return (PUBNUB_OK == result) ? EXIT_SUCCESS : EXIT_FAILURE;
}
```

This sample is adapted from `examples/channel_groups/add_channels.c` (cooperative polling).

### Returns

`pubnub_channel_group_add_channels()` returns a `pubnub_future_t`. On success, the future's status is `PUBNUB_OK` with no additional payload. There is no dedicated result-accessor function for this operation. Check the status only.

## Remove channels from a channel group

`pubnub_channel_group_remove_channels()` removes one or more channels from a channel group. The group itself is not deleted, even if this call removes its last remaining channel. Use [Delete a channel group](#delete-a-channel-group) to remove the group.

### Method(s)

```c
pubnub_future_t pubnub_channel_group_remove_channels(pubnub_context_t* ctx, const pubnub_channel_group_remove_opts_t* opts);
```

| Parameter | Description |
| --- | --- |
| `channel_group` *Type: `const char*`Default: — | Borrowed, NUL-terminated. Target channel group name. |
| `channels` *Type: `const char*`Default: — | Borrowed, NUL-terminated. Comma-separated channel names to remove, for example `"ch1,ch2"`. Not an array. |
| `timeout_ms`Type: `uint32_t`Default: `0` (inherits `pubnub_config_t::transaction_timeout_ms`) | A non-zero value overrides the context-level timeout for this call only. |

Zero-initialize with `PUBNUB_CHANNEL_GROUP_REMOVE_OPTS_INIT` (expands to `{0}`).

**C-family contract**

* **Header** — `#include <pubnub/features/channel_groups.h>`
* **Types** — `pubnub_channel_group_remove_opts_t`
* **Prerequisite** — an initialized context with `subscribe_key` and `user_id` set; see [Add channels](#add-channels-to-a-channel-group)
* **Feature flag** — `PUBNUB_ENABLE_CHANNEL_GROUPS`
* **Ownership / lifetime** — `channel_group` and `channels` are borrowed, NUL-terminated strings
* **Blocking** — never blocks; returns a `pubnub_future_t` immediately

### Sample code

```c
#include <pubnub/client.h>
#include <pubnub/features/channel_groups.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_channel_group_remove_opts_t opts = PUBNUB_CHANNEL_GROUP_REMOVE_OPTS_INIT;
    opts.channel_group = "my_channel_group";
    opts.channels       = "ch2";

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

    pubnub_res_t result = pubnub_future_status(future);
    if (PUBNUB_OK == result) {
        printf("channels removed from group\n");
    }
    else {
        printf("remove channels failed: %d\n", (int)result);
    }

    pubnub_future_release(future);
    pubnub_destroy(ctx);
    return (PUBNUB_OK == result) ? EXIT_SUCCESS : EXIT_FAILURE;
}
```

This sample is adapted from `examples/channel_groups/remove_channels.c` (cooperative polling).

### Returns

`pubnub_channel_group_remove_channels()` returns a `pubnub_future_t`. On success, the future's status is `PUBNUB_OK` with no additional payload. There is no dedicated result-accessor function for this operation.

## List channels in a channel group

`pubnub_channel_group_list_channels()` lists every channel currently in a channel group.

### Method(s)

```c
pubnub_future_t pubnub_channel_group_list_channels(pubnub_context_t* ctx, const pubnub_channel_group_list_opts_t* opts);
```

| Parameter | Description |
| --- | --- |
| `channel_group` *Type: `const char*`Default: — | Borrowed, NUL-terminated. Target channel group name. |
| `timeout_ms`Type: `uint32_t`Default: `0` (inherits `pubnub_config_t::transaction_timeout_ms`) | A non-zero value overrides the context-level timeout for this call only. |

This options struct has no `channels` field. Listing takes only the group name. Zero-initialize with `PUBNUB_CHANNEL_GROUP_LIST_OPTS_INIT` (expands to `{0}`).

**C-family contract**

* **Header** — `#include <pubnub/features/channel_groups.h>`
* **Types** — `pubnub_channel_group_list_opts_t`, `pubnub_channel_group_list_result_t`
* **Prerequisite** — an initialized context with `subscribe_key` and `user_id` set; see [Add channels](#add-channels-to-a-channel-group)
* **Feature flag** — `PUBNUB_ENABLE_CHANNEL_GROUPS`
* **Ownership / lifetime** — `channel_group` is borrowed, NUL-terminated. Every `pubnub_string_view_t` returned by the result accessors below aliases internal response data. That data stays valid only until `pubnub_future_release()` is called on the same future, so copy the bytes out first if you need them afterward.
* **Blocking** — never blocks; returns a `pubnub_future_t` immediately

### Sample code

```c
#include <pubnub/client.h>
#include <pubnub/features/channel_groups.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_channel_group_list_opts_t opts = PUBNUB_CHANNEL_GROUP_LIST_OPTS_INIT;
    opts.channel_group = "my_channel_group";

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

    pubnub_res_t result = pubnub_future_status(future);
    if (PUBNUB_OK == result) {
        pubnub_channel_group_list_result_t list = pubnub_channel_group_list_result(future);
        for (uint32_t i = 0; i < list.count; i++) {
            pubnub_string_view_t ch = pubnub_channel_group_list_result_channel_at(future, i);
            printf("  %.*s\n", (int)ch.len, ch.ptr);
        }
    }
    else {
        printf("list channels failed: %d\n", (int)result);
    }

    pubnub_future_release(future);
    pubnub_destroy(ctx);
    return (PUBNUB_OK == result) ? EXIT_SUCCESS : EXIT_FAILURE;
}
```

This sample is adapted from `examples/channel_groups/list_channels.c` (cooperative polling). Print with `%.*s`, exactly as shown. `pubnub_string_view_t` values are never NUL-terminated.

### Returns

```c
pubnub_channel_group_list_result_t   pubnub_channel_group_list_result(pubnub_future_t future);
pubnub_string_view_t                 pubnub_channel_group_list_result_channel_at(pubnub_future_t future, size_t index);
```

Call `pubnub_channel_group_list_result(future)` after the future completes with `PUBNUB_OK`. It returns `pubnub_channel_group_list_result_t`, a struct with a single field, `count`, the number of channels in the group. It is zero-initialized if the future is not ready or invalid.

Then iterate `0 <= i < count`, calling `pubnub_channel_group_list_result_channel_at(future, i)` for each index to get a `pubnub_string_view_t` naming that channel. An out-of-range index returns a zero-initialized `{NULL, 0}` view rather than crashing.

### Other examples

#### List channels with a callback

Adapted from `examples/channel_groups/list_channels_async.c`, which composes an add-then-list-then-delete flow as a teaching example. The listing step alone, in isolation, looks like this:

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

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

static volatile int s_list_done = 0;

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

    if (PUBNUB_OK == status) {
        pubnub_channel_group_list_result_t list = pubnub_channel_group_list_result(future);
        for (uint32_t i = 0; i < list.count; i++) {
            pubnub_string_view_t ch = pubnub_channel_group_list_result_channel_at(future, i);
            printf("  %.*s\n", (int)ch.len, ch.ptr);
        }
    }
    else {
        printf("list channels failed: %d\n", (int)status);
    }

    pubnub_future_release(future);
    s_list_done = 1;
}

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_channel_group_list_opts_t opts = PUBNUB_CHANNEL_GROUP_LIST_OPTS_INIT;
    opts.channel_group = "my_channel_group";

    pubnub_future_t future = pubnub_channel_group_list_channels(ctx, &opts);
    pubnub_async(future, on_list_complete, NULL);

    while (!s_list_done) {
        pubnub_process(ctx);
    }

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

## Delete a channel group

`pubnub_channel_group_remove()` deletes a channel group entirely, including its membership list. This is a distinct operation from [Remove channels from a channel group](#remove-channels-from-a-channel-group). That call removes individual channels and leaves the (possibly empty) group in place. This call removes the group itself.

### Method(s)

```c
pubnub_future_t pubnub_channel_group_remove(pubnub_context_t* ctx, const pubnub_channel_group_remove_group_opts_t* opts);
```

| Parameter | Description |
| --- | --- |
| `channel_group` *Type: `const char*`Default: — | Borrowed, NUL-terminated. Channel group to delete. |
| `timeout_ms`Type: `uint32_t`Default: `0` (inherits `pubnub_config_t::transaction_timeout_ms`) | A non-zero value overrides the context-level timeout for this call only. |

Zero-initialize with `PUBNUB_CHANNEL_GROUP_REMOVE_GROUP_OPTS_INIT` (expands to `{0}`).

**C-family contract**

* **Header** — `#include <pubnub/features/channel_groups.h>`
* **Types** — `pubnub_channel_group_remove_group_opts_t`
* **Prerequisite** — an initialized context with `subscribe_key` and `user_id` set; see [Add channels](#add-channels-to-a-channel-group)
* **Feature flag** — `PUBNUB_ENABLE_CHANNEL_GROUPS`
* **Ownership / lifetime** — `channel_group` is borrowed, NUL-terminated
* **Blocking** — never blocks; returns a `pubnub_future_t` immediately

### Sample code

```c
#include <pubnub/client.h>
#include <pubnub/features/channel_groups.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_channel_group_remove_group_opts_t opts = PUBNUB_CHANNEL_GROUP_REMOVE_GROUP_OPTS_INIT;
    opts.channel_group = "my_channel_group";

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

    pubnub_res_t result = pubnub_future_status(future);
    if (PUBNUB_OK == result) {
        printf("channel group deleted\n");
    }
    else {
        printf("delete group failed: %d\n", (int)result);
    }

    pubnub_future_release(future);
    pubnub_destroy(ctx);
    return (PUBNUB_OK == result) ? EXIT_SUCCESS : EXIT_FAILURE;
}
```

This sample is adapted from `examples/channel_groups/delete_group.c` (cooperative polling).

### Returns

`pubnub_channel_group_remove()` returns a `pubnub_future_t`. On success, the future's status is `PUBNUB_OK` with no additional payload. There is no dedicated result-accessor function for this operation.

## Error handling

`pubnub_res_t` is the single status type shared across every feature in this SDK, including channel groups. There are no per-feature error accessors. For the full result-code catalog and the `pubnub_response_service_error()` pattern for server-side error detail, see [Status Events](https://www.pubnub.com/docs/sdks/c/status-events.md).

Two behaviors specific to channel groups are worth calling out:

* Passing `NULL` for `ctx` or `opts` on any of the four functions on this page fails immediately with `PUBNUB_ERR_INVALID_ARGUMENT`, readable via `pubnub_future_status()` without polling or awaiting first.
* On the wire, an HTTP 4xx response maps to `PUBNUB_ERR_SERVER` regardless of the response body, and a `200` response whose body contains `"error":true` also maps to `PUBNUB_ERR_SERVER`. Call `pubnub_response_service_error()` for normalized detail in either case. See [Retrieving server error detail](https://www.pubnub.com/docs/sdks/c/status-events.md#retrieving-server-error-detail).

## 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
