---
source_url: https://www.pubnub.com/docs/chat/swift-chat-sdk/build/features/channels/channel-groups
title: Channel groups
updated_at: 2026-06-01T12:02:02.477Z
---

> Documentation Index
> For a curated overview of PubNub documentation, see: https://www.pubnub.com/docs/llms.txt
> For the full list of all documentation pages, see: https://www.pubnub.com/docs/llms-full.txt


# Channel groups

Channel groups let you subscribe to multiple channels with a single subscription. Add channels to a group and receive messages from all of them at once.

Use channel groups to:

* Subscribe to all user channels with one call
* Monitor presence across multiple channels
* Manage subscriptions dynamically without reconnecting

Publishing to a channel group is not supported. Publish to each channel individually.

:::note Requires Stream Controller
Enable *Stream Controller* for your key in the [Admin Portal](https://admin.pubnub.com/). See the [support page](https://support.pubnub.com/hc/en-us/articles/360051974791-How-do-I-enable-add-on-features-for-my-keys-) for details.
:::

## Get channel group reference

Get a reference to a channel group with `getChannelGroup()`. This returns a handle to manage the group without creating it on the server.

If the group exists, the reference points to it. Otherwise, the handle creates the group when you add channels.

### Method signature

This method takes the following parameters:

```swift
chat.getChannelGroup(id: String) -> ChannelGroupImpl
```

#### Input

| Parameter | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| id | String | Yes |  | Unique identifier for the channel group. |

#### Output

| Type | Description |
| --- | --- |
| `ChannelGroupImpl` | A [ChannelGroup](https://www.pubnub.com/docs/chat/swift-chat-sdk/learn/chat-entities/channel-group) object you can use to manage the group. |

### Sample code

:::tip Sample code
The code samples in Swift Chat SDK focus on asynchronous code execution.
You can also write synchronous code as the parameters are shared between the async and sync methods but we don't provide usage examples of such.
:::

Get a reference to a channel group named `my-channel-group`.

```swift
// Assumes a "ChatImpl" reference named "chat"
let channelGroup = chat.getChannelGroup(id: "my-channel-group")
```

## Remove channel group

Delete a channel group from the server with `removeChannelGroup()`.

### Method signature

This method takes the following parameters:

```swift
chat.removeChannelGroup(id: String) async throws
```

#### Input

| Parameter | Description |
| --- | --- |
| `id` *Type: `String`Default: n/a | Unique identifier of the channel group to remove. |

#### Output

This method doesn't return any value.

### Sample code

Remove a channel group named `my-channel-group`.

```swift
// Assumes a "ChatImpl" reference named "chat"
Task {
  try await chat.removeChannelGroup(id: "my-channel-group")
  debugPrint("Channel group removed successfully")
}
```

## List channels

Get a paginated list of all channels in a channel group with `listChannels()`.

### Method signature

This method takes the following parameters:

```swift
channelGroup.listChannels(
    filter: String? = nil,
    sort: [PubNub.ObjectSortField] = [],
    limit: Int? = nil,
    page: PubNubHashedPage? = nil
) async throws -> (channels: [ChannelImpl], page: PubNubHashedPage?)
```

#### Input

| Parameter | Description |
| --- | --- |
| `filter`Type: `String`Default: `nil` | Expression used to filter the results. Returns only the channels whose properties satisfy the given expression. The filter language is [defined here](https://www.pubnub.com/docs/general/metadata/filtering). |
| `sort`Type: `[PubNub.ObjectSortField]`Default: `[]` | An array to specify the sort order. Available options are `id`, `name`, and `updated`. Use `ascending` or `descending` to specify the sorting direction. |
| `limit`Type: `Int`Default: `nil` | Number of objects to return in response. The maximum value is `100`. |
| `page`Type: `PubNubHashedPage`Default: `nil` | Object used for pagination to define which previous or next result page you want to fetch. |

#### Output

| Type | Description |
| --- | --- |
| `(channels: [ChannelImpl], page: PubNubHashedPage?)` | A tuple containing a set of channels with pagination information. |

### Sample code

List all channels in a channel group.

```swift
// Assumes a "ChatImpl" reference named "chat"
Task {
  let channelGroup = chat.getChannelGroup(id: "my-channel-group")
  let result = try await channelGroup.listChannels()
    
  for channel in result.channels {
    debugPrint("Channel: \(channel.id)")
  }
}
```

## Add channels

Add `Channel` entities to a channel group with `add(channels:)`.

### Method signature

This method takes the following parameters:

```swift
channelGroup.add(channels: [ChannelImpl]) async throws
```

#### Input

| Parameter | Description |
| --- | --- |
| `channels` *Type: `[ChannelImpl]`Default: n/a | Array of [Channel](https://www.pubnub.com/docs/chat/swift-chat-sdk/learn/chat-entities/channel) entities to add to the group. |

#### Output

This method doesn't return any value.

### Sample code

Add two channels to a channel group.

```swift
// Assumes a "ChatImpl" reference named "chat"
Task {
  let channelGroup = chat.getChannelGroup(id: "my-channel-group")
  let supportChannel = try await chat.getChannel(channelId: "support-channel")
  let generalChannel = try await chat.getChannel(channelId: "general-channel")
    
  if let supportChannel, let generalChannel {
    try await channelGroup.add(channels: [supportChannel, generalChannel])
    debugPrint("Channels added successfully")
  }
}
```

## Add channel identifiers

Add channels to a group by ID with `addChannelIdentifiers(_:)`. This avoids fetching full `Channel` entities.

:::note No validation
This method does not validate that the channel IDs exist.
:::

### Method signature

This method takes the following parameters:

```swift
channelGroup.addChannelIdentifiers(_ ids: [String]) async throws
```

#### Input

| Parameter | Description |
| --- | --- |
| `ids` *Type: `[String]`Default: n/a | Array of channel IDs to add to the group. |

#### Output

This method doesn't return any value.

### Sample code

Add channels by their IDs.

```swift
// Assumes a "ChatImpl" reference named "chat"
Task {
  let channelGroup = chat.getChannelGroup(id: "my-channel-group")
  try await channelGroup.addChannelIdentifiers(["support-channel", "general-channel"])
  debugPrint("Channel identifiers added successfully")
}
```

## Remove channels

Remove `Channel` entities from a channel group with `remove(channels:)`.

### Method signature

This method takes the following parameters:

```swift
channelGroup.remove(channels: [ChannelImpl]) async throws
```

#### Input

| Parameter | Description |
| --- | --- |
| `channels` *Type: `[ChannelImpl]`Default: n/a | Array of [Channel](https://www.pubnub.com/docs/chat/swift-chat-sdk/learn/chat-entities/channel) entities to remove from the group. |

#### Output

This method doesn't return any value.

### Sample code

Remove channels from a channel group.

```swift
// Assumes a "ChatImpl" reference named "chat"
// Assumes "ChannelImpl" references named "supportChannel" and "generalChannel"
Task {
  let channelGroup = chat.getChannelGroup(id: "my-channel-group")
  try await channelGroup.remove(channels: [supportChannel, generalChannel])
  debugPrint("Channels removed successfully")
}
```

## Remove channel identifiers

Remove channels from a group by ID with `removeChannelIdentifiers(_:)`.

:::note No validation
This method does not validate that the channel IDs exist in the group.
:::

### Method signature

This method takes the following parameters:

```swift
channelGroup.removeChannelIdentifiers(_ ids: [String]) async throws
```

#### Input

| Parameter | Description |
| --- | --- |
| `ids` *Type: `[String]`Default: n/a | Array of channel IDs to remove from the group. |

#### Output

This method doesn't return any value.

### Sample code

Remove channels by their IDs.

```swift
// Assumes a "ChatImpl" reference named "chat"
Task {
  let channelGroup = chat.getChannelGroup(id: "my-channel-group")
  try await channelGroup.removeChannelIdentifiers(["support-channel", "general-channel"])
  debugPrint("Channel identifiers removed successfully")
}
```

## Watch channel group

Subscribe to all channels in the group and receive messages with `onMessageReceived()`. The callback fires whenever a message arrives on any channel in the group.

You can also use `channelGroup.stream.messages()` for an `AsyncStream`-based approach.

:::note Deprecation
`connect()` is deprecated. Use `onMessageReceived()` (closure-based) or `channelGroup.stream.messages()` (AsyncStream-based) instead.
:::

### Method signature

```swift
channelGroup.onMessageReceived(
    callback: @escaping (MessageImpl) -> Void
) -> AutoCloseable
```

#### Input

| Parameter | Description |
| --- | --- |
| `callback` *Type: `(MessageImpl) -> Void`Default: n/a | Closure called whenever a new message is published on any channel in the group. |

#### Output

| Type | Description |
| --- | --- |
| `AutoCloseable` | An object you must retain. When released or closed, the listener stops. |

### Sample code

Start receiving messages from all channels in a group.

#### AsyncStream

```swift
// Assumes a "ChatImpl" reference named "chat"
Task {
  let channelGroup = chat.getChannelGroup(id: "my-channel-group")
    
  for await message in channelGroup.connect() {
    debugPrint("Received message on channel \(message.channelId): \(message.text)")
  }
}
```

#### Closure

```swift
// Assumes a "ChannelGroupImpl" reference named "channelGroup"
  
// Important: Keep a strong reference to the returned "AutoCloseable" object as long as you want
// to receive updates. If the "AutoCloseable" is deallocated, the stream will be cancelled,
// and no further items will be produced. You can also stop receiving messages manually
// by calling the "close()" method on the "AutoCloseable" object.
autoCloseable = channelGroup.connect { message in
  debugPrint("Received message on channel \(message.channelId): \(message.text)")
}
```

## Get present users

Get a list of users currently present on any channel in the group with `whoIsPresent()`.

:::note Requires Presence
Enable [Presence](https://youtu.be/i2yLIqmvFD0) for your keyset in the [Admin Portal](https://admin.pubnub.com/).
:::

### Method signature

This method has the following signature:

```swift
channelGroup.whoIsPresent(
    limit: Int = 1000,
    offset: Int? = 0
) async throws -> [String: [String]]
```

#### Input

| Parameter | Description |
| --- | --- |
| `limit`Type: `Int`Default: `1000` | Maximum number of occupants to return per channel. Valid range: 0-1000. Use `0` to get occupancy counts without user details. |
| `offset`Type: `Int?`Default: `0` | Zero-based starting index for pagination. Returns occupants starting from this position in the list. Must be >= 0. |

#### Output

| Type | Description |
| --- | --- |
| `[String: [String]]` | A dictionary where each key is a channel ID and the value is an array of user IDs present on that channel. |

### Sample code

Get all users present on channels in a group.

```swift
// Assumes a "ChatImpl" reference named "chat"
Task {
  let channelGroup = chat.getChannelGroup(id: "my-channel-group")
  let presenceByChannel = try await channelGroup.whoIsPresent()
    
  for (channelId, userIds) in presenceByChannel {
    debugPrint("Channel \(channelId) has users: \(userIds)")
  }
}
```

## Stream presence

Receive real-time updates when users join or leave any channel in the group with `onPresenceChanged()`.

You can also use `channelGroup.stream.presenceChanges()` for an `AsyncStream`-based approach.

:::note Requires Presence
Enable [Presence](https://youtu.be/i2yLIqmvFD0) for your keyset in the [Admin Portal](https://admin.pubnub.com/).
:::

:::note Deprecation
`streamPresence()` is deprecated. Use `onPresenceChanged()` (closure-based) or `channelGroup.stream.presenceChanges()` (AsyncStream-based) instead.
:::

### Method signature

```swift
channelGroup.onPresenceChanged(
    callback: @escaping ([String: [String]]) -> Void
) -> AutoCloseable
```

#### Input

| Parameter | Description |
| --- | --- |
| `callback` *Type: `([String: [String]]) -> Void`Default: n/a | Closure called with a dictionary of channel IDs to present user ID arrays whenever presence changes. |

#### Output

| Type | Description |
| --- | --- |
| `AutoCloseable` | An object you must retain. When released or closed, the listener stops. |

### Sample code

Stream presence updates for all channels in a group.

#### AsyncStream

```swift
// Assumes a "ChatImpl" reference named "chat"
Task {
  let channelGroup = chat.getChannelGroup(id: "my-channel-group")
    
  for await presenceByChannel in channelGroup.streamPresence() {
    for (channelId, userIds) in presenceByChannel {
      debugPrint("Channel \(channelId) now has users: \(userIds)")
    }
  }
}
```

#### Closure

```swift
// Assumes a "ChannelGroupImpl" reference named "channelGroup"
  
// Important: Keep a strong reference to the returned "AutoCloseable" object as long as you want
// to receive updates. If the "AutoCloseable" is deallocated, the stream will be cancelled,
// and no further items will be produced. You can also stop receiving presence updates manually
// by calling the "close()" method on the "AutoCloseable" object.
autoCloseable = channelGroup.streamPresence { presenceByChannel in
  for (channelId, userIds) in presenceByChannel {
    debugPrint("Channel \(channelId) now has users: \(userIds)")
  }
}
```