---
source_url: https://www.pubnub.com/docs/sdks/dart/api-reference/publish-and-subscribe
title: Publish/Subscribe API for Dart SDK
updated_at: 2026-06-26T11:05:38.049Z
sdk_name: PubNub Dart SDK
sdk_version: 7.1.0
---

> 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


# Publish/Subscribe API for Dart SDK

PubNub Dart SDK, use the latest version: 7.1.0

Install:

```bash
dart pub add pubnub@7.1.0
```

PubNub delivers messages worldwide in less than 30 ms. Send a message to one recipient or broadcast to thousands of subscribers.

For higher-level conceptual details on publishing and subscribing, refer to [Connection Management](https://www.pubnub.com/docs/general/setup/connection-management) and to [Publish Messages](https://www.pubnub.com/docs/general/messages/publish).

## Publish

`publish()` sends a message to all channel subscribers. PubNub replicates the message across its points of presence and delivers it to all subscribed clients on that channel.

### Prerequisites and limitations

* You must [initialize PubNub](https://www.pubnub.com/docs/sdks/dart/api-reference/configuration) with the `publishKey`.
* You don't have to be subscribed to a channel to publish to it.
* You cannot publish to multiple channels simultaneously.

### Security

Secure messages with Transport Layer Security (TLS) or Secure Sockets Layer (SSL) by setting `ssl` to `true` during [initialization](https://www.pubnub.com/docs/sdks/dart/api-reference/configuration). You can also [encrypt](https://www.pubnub.com/docs/sdks/dart/api-reference/configuration#cryptomodule) messages.

### Message data

The message can contain any JavaScript Object Notation (JSON)-serializable data (objects, arrays, integers, strings). Avoid special classes or functions. Strings can include any UTF‑8 characters.

:::warning Don't JSON serialize
You should not JSON serialize the `message` and `meta` parameters when sending signals, messages, or files as the serialization is automatic. Pass the full object as the message/meta payload and let PubNub handle everything.
:::

### Size

The maximum message size is 32 KiB. This includes the escaped character count and the channel name. Aim for under 1,800 bytes for optimal performance.

If your message exceeds the limit, you'll receive a `Message Too Large` error. To learn more or calculate payload size, see [Message size limits](https://www.pubnub.com/docs/general/messages/publish#message-size-limit).

:::tip Need larger messages?
Our platform is optimized for payloads up to 32 KiB. PubNub supports larger messages, but increasing the limit requires a verification of compatibility with your use case.
Talk to [our team](https://www.pubnub.com/company/contact-sales/) to discuss increasing the message size limit for your use case.
:::

### Publish rate

You can publish as fast as bandwidth allows. There is a [soft throughput limit](https://www.pubnub.com/docs/general/setup/limits) because messages may drop if subscribers can't keep up.

For example, publishing 200 messages at once may cause the first 100 to drop if a subscriber hasn't received any yet. The in-memory queue stores only 100 messages.

### Custom message type

You can optionally provide the `customMessageType` parameter to add your business-specific label or category to the message, for example `text`, `action`, or `poll`.

### Best practices

* Publish to a channel serially (not concurrently).
* Verify a success return code (for example, `[1,"Sent","136074940..."]`).
* Publish the next message only after a success return code.
* On failure (`[0,"blah","<timetoken>"]`), retry.
* Keep the in-memory queue under 100 messages to avoid drops.
* Throttle bursts to meet latency needs (for example, no more than 5 messages per second).

### Method(s)

To Publish a message you can use the following method(s) in the Dart SDK:

```dart
pubnub.publish(
  String channel,
  dynamic message,
  {Keyset? keyset,
  String? using,
  dynamic meta,
  bool? storeMessage,
  int? ttl,
  String? customMessageType}
) 
```

| Parameter | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| channel | String | Yes |  | Destination of the `message` (channel ID). |
| message | Any | Yes |  | The payload. |
| keyset | Keyset | Optional |  | Override for the PubNub default keyset configuration. |
| using | String | Optional |  | Keyset name from the `keysetStore` to be used for this method call. |
| meta | dynamic | Optional | `Not set` | Metadata object which can be used with the filtering ability. |
| storeMessage | bool | Optional | `Account default` | Store message in history. If not specified, the decision depends on whether Message Persistence has been enabled for the key or not. |
| ttl | int | Optional |  | Set a per message time to live in Message Persistence. 1. If `storeMessage = true`, and `ttl = 0`, the message is stored with no expiry time. 2. If `storeMessage = true` and `ttl = X` (`X` is an Integer value), the message is stored with an expiry time of `X` hours unless you have message retention set to `Unlimited` on your keyset configuration in the Admin Portal. 3. If `storeMessage = false`, the `ttl` parameter is ignored. 4. If `ttl` isn't specified, then expiration of the message defaults back to the expiry value for the key. |
| customMessageType | String | Optional |  | A case-sensitive, alphanumeric string from 3 to 50 characters describing the business-specific label or category of the message. Dashes `-` and underscores `_` are allowed. The value cannot start with special characters or the string `pn_` or `pn-`. Examples: `text`, `action`, `poll`. |

### Sample code

:::tip Reference code
This example is a self-contained code snippet ready to be run. It includes necessary imports and executes methods with console logging. Use it as a reference when working with other examples in this document.
:::

Publish a message to a channel:

```dart
import 'package:pubnub/pubnub.dart';

void main() async {
  // Create PubNub instance with default keyset.
  var pubnub = PubNub(
    defaultKeyset: Keyset(
      subscribeKey: 'demo',
      publishKey: 'demo',
      userId: UserId('myUniqueUserId'),
    ),
  );

  // Channel to publish the message to
  String channel = 'myChannel';

  // Message to publish
  String message = 'hello world!';

  try {
    // Publish the message
    var result = await pubnub.publish(
      channel,
      message,
      customMessageType: 'text-message',
    );

    // Print the publish result
    print('Message "${result.description}" with timetoken: ${result.timetoken}');
  } catch (e) {
    print('Error publishing message: $e');
  }
}
```

### Returns

The `publish()` operation returns a `PublishResult` which contains the following operations:

| Method | Description |
| --- | --- |
| `description`Type: `String` | The description, for example `Sent`. |
| `timetoken`Type: `int` | Returns an `int` representation of the timetoken when the message was published. |

### Other examples

#### Publish with metadata

```dart
var result = await pubnub.publish('my_channel', 'hello', meta: '<json data>', customMessageType: 'text-message');
```

#### Publishing JsonObject (Google GSON)

```dart
var message = {'hello': 'world'};
var result = await pubnub.publish('my_channel', message, customMessageType: 'text-message');
```

#### Publishing JsonArray (Google GSON)

```dart
var message = ['hello', 'world'];
var result = await pubnub.publish('my_channel', message, customMessageType: 'text-message');
```

#### Publishing JSONObject (org.json)

```dart
var jsonString = '{"score": 40}';
var result = await pubnub.publish('my_channel', jsonDecode(jsonString), customMessageType: 'text-message');
```

#### Publishing JSONArray (org.json)

```dart
var jsonString = '''
[
  {"score": 40},
  {"score": 80}
]
''';
var result = await pubnub.publish('my_channel', jsonDecode(jsonString), customMessageType: 'text-message');
```

#### Store the published message for 10 hours

```dart
var result =
    await pubnub.publish('my_channel', 'hello', storeMessage: true, ttl: 10, customMessageType: 'text-message');
```

## Signal

The `signal()` function sends a signal to all subscribers of a channel.

By default, signals are limited to a message payload size of `64` bytes. This limit applies only to the payload, and not to the URI or headers. If you require a larger payload size, please [contact support](https://www.pubnub.com/docs/mailto:support@pubnub.com).

### Method(s)

To Signal a message you can use the following method(s) in the Dart SDK:

```dart
pubnub.signal(
  String channel,
  dynamic message,
  {Keyset? keyset,
  String? using,
  String? customMessageType}
) 
```

| Parameter | Description |
| --- | --- |
| `channel` *Type: `String` | Destination of the `message` (channel ID). |
| `message` *Type: `Any` | The payload. |
| `keyset`Type: `Keyset` | Override for the PubNub default keyset configuration. |
| `using`Type: `String` | Keyset name from the `keysetStore` to be used for this method call. |
| `customMessageType`Type: `String` | A case-sensitive, alphanumeric string from 3 to 50 characters describing the business-specific label or category of the message. Dashes `-` and underscores `_` are allowed. The value cannot start with special characters or the string `pn_` or `pn-`. Examples: `text`, `action`, `poll`. |

### Sample code

Signal a message to a channel:

```dart
var result = await pubnub.signal('myChannel', 'signal!', customMessageType: 'text-message');
```

### Response

The `signal()` operation returns a `SignalResult` which contains the following operations:

| Method | Description |
| --- | --- |
| `description`Type: `String` | The description, for example `Sent`. |
| `timetoken`Type: `int` | Returns an `int` representation of the timetoken when the signal was published. |

## Subscribe

### Receive messages

Your app receives messages and events via event listeners. The event listener is a single point through which your app receives all the messages, signals, and events that are sent in any channel you are subscribed to.

For more information about adding a listener, refer to the [Subscription](#subscription) section.

:::note No built-in event throttling
The PubNub SDK delivers every incoming event to your listener as it arrives — there is no built-in throttling or rate-limiting on the subscriber side. If you need to control how often your application processes events, wrap your listener callback with a throttle or debounce utility from your language or framework ecosystem.
To reduce the number of messages delivered to your client in the first place, use [Subscribe Filters](https://www.pubnub.com/docs/general/channels/subscribe-filters) to filter messages server-side before they reach your listener.
:::

### Description

This function causes the client to create an open TCP socket to the PubNub Real-Time Network and begin listening for messages on a specified `channel` ID. To subscribe to a `channel` ID the client must send the appropriate `subscribeKey` at initialization.

By default a newly subscribed client will only receive messages published to the channel after the `subscribe()` call completes.

If a client gets disconnected from a channel, it can automatically attempt to reconnect to that `channel` ID and retrieve any available messages that were missed during that period. This can be achieved by setting `retryPolicy` to `RetryPolicy.linear`, when [initializing](https://www.pubnub.com/docs/sdks/dart/api-reference/configuration) the client.

:::warning Unsubscribing from all channels
**Unsubscribing** from all channels, and then **subscribing** to a new channel Y is not the same as subscribing to channel Y and then unsubscribing from the previously subscribed channel(s). Unsubscribing from all channels resets the last-received `timetoken` and thus, there could be some gaps in the subscription that may lead to message loss.
:::

### Method(s)

To Subscribe to a channel you can use the following method(s) in the Dart SDK:

```dart
pubnub.subscribe(
  {Set<String>? channels,
  Set<String>? channelGroups,
  bool withPresence = false,
  Timetoken? timetoken,
  Keyset? keyset,
  String? using}
) 
```

| Parameter | Description |
| --- | --- |
| `channels` *Type: `Set<String>` | `channels` to subscribe to. Either `channel` ID or `channelGroup` is required. |
| `channelGroups` *Type: `Set<String>` | `channelGroups` to subscribe to. Either `channel` ID or `channelGroup` is required. |
| `withPresence`Type: `bool` | Also subscribe to related presence information. For information on how to receive presence events and what those events are, refer to [Presence Events](https://www.pubnub.com/docs/general/presence/presence-events#subscribe-to-presence-channel). |
| `timetoken`Type: `Timetoken` | Timetoken to start the subscription from. |
| `keyset`Type: `Keyset` | Override for the PubNub default keyset configuration. |
| `using`Type: `String` | Keyset name from the `keysetStore` to be used for this method call. |

### Sample code

Subscribe to a channel:

```dart
var channel = "my_channel";
var subscription = pubnub.subscribe(channels: {channel});
```

### Returns

The `subscribe()` method returns a `Subscription`. For more information, refer to [Subscription](#subscription).

### Subscription

A `Subscription` contains a Dart stream of messages from the channel(s) to which you are subscribed. You can transform that stream in the usual ways, or add a listener using `listen`.

### Listeners

For a list of available listeners, refer to the [Listeners](https://www.pubnub.com/docs/sdks/dart/api-reference/configuration#event-listeners) section.

##### Cancel

The `cancel()` method cancels the subscription. This disposes of internal streams, so the subscription becomes unusable.

###### Method(s)

```dart
subscription.cancel();
```

###### Sample code

```dart
// var subscription = pubnub.subscribe(channels: {'my_channel'});
// active subscription available
await subscription.cancel();
```

###### Returns

The `cancel()` operation on `Subscription` doesn't return any object. It disposes of internal streams, so the subscription becomes unusable.

##### Dispose

The `dispose()` method is an alias for the `cancel()` method.

##### Subscribe

The `subscribe()` method is an alias for the `resume()` method with additional reconnection logic. If the subscription is not intentionally paused, it reconnects the subscription. Otherwise, it resumes the subscription.

###### Method(s)

```dart
subscription.subscribe()
```

###### Sample code

```dart
var subscription = pubnub.subscription(channels: {'my_channel'});
// Later, activate the subscription
subscription.subscribe();
```

##### Unsubscribe

The `unsubscribe()` method is an alias for the `pause()` method.

##### Pause

Pausing a subscription prevents the message and presence streams from emitting messages. Keep in mind that you may miss messages while subscription is paused. If subscription is currently paused, this method is a no-op.

###### Method(s)

```dart
subscription.pause() 
```

###### Sample code

```dart
var subscription = pubnub.subscribe(channels: {'my_channel'});
// active subscription available
subscription.pause();
```

###### Returns

The `pause()` operation on `Subscription` doesn't return any object. Pausing subscription prevents the `messages` and `presence` streams from emitting messages.

##### Resume

Resumes a paused subscription. If a subscription isn't paused, then this method is a no-op.

###### Method(s)

```dart
subscription.resume() 
```

###### Sample code

```dart
// If subscription is paused
subscription.resume();
```

###### Returns

The `resume()` operation on `Subscription` object doesn't return any object. If subscription isn't paused, then this method does nothing.

##### Restore

Restores the subscription. This method is useful when a subscription encounters an error and you want to recover without creating a new subscription.

###### Method(s)

```dart
subscription.restore()
```

###### Sample code

```dart
var subscription = pubnub.subscribe(channels: {'my_channel'});

subscription.messages.listen(
  (message) {
    print('Received: ${message.content}');
  },
  onError: (error) async {
    print('Error occurred: $error');
    // Restore the subscription after an error
    await subscription.restore();
  }
);
```

###### Returns

The `restore()` operation returns a `Future<void>` that completes when the subscription has been restored.

### Other examples

#### Basic subscribe with logging

```dart
// Create a root logger
var logger = StreamLogger.root('root', logLevel: Level.all);

// Subscribe to messages with a default printer
var sub = logger.stream.listen(
    LogRecord.createPrinter(r'[$time] (${level.name}) $scope: $message'));

// Provide logging only for the parts that you are interested in.
var _ = await provideLogger(logger, () async {
  var subscription = pubnub.subscribe(channels: {'test'});

  var message = subscription.messages.first;

  await pubnub.publish('test', {'message': 'My message'});

  print(await message);

  await subscription.dispose();
});
```

#### Unsubscribe from all channels

Cancels all existing subscriptions across all keysets.

```dart
// Create subscriptions
var subscription1 = pubnub.subscribe(channels: {'channel1'});
var subscription2 = pubnub.subscribe(channels: {'channel2', 'channel3'});

// Later, unsubscribe from all channels
await pubnub.unsubscribeAll();
```

:::warning Unsubscribing from all channels
Unsubscribing from all channels resets the last-received `timetoken`.
:::

#### Subscribing to multiple channels

It's possible to subscribe to more than one channel using the [Multiplexing](https://www.pubnub.com/docs/general/channels/subscribe#channel-multiplexing) feature. The example shows how to do that using an array to specify the channel names.

:::note Alternative subscription methods
You can also use [Wildcard Subscribe](https://www.pubnub.com/docs/general/channels/subscribe#wildcard-subscribe) and [Channel Groups](https://www.pubnub.com/docs/general/channels/subscribe#channel-groups) to subscribe to multiple channels at a time. To use these features, the *Stream Controller* add-on must be enabled on your keyset in the [Admin Portal](https://admin.pubnub.com).
:::

```dart
var subscription = pubnub.subscribe(channels: {'my_channel', 'channel1'});
```

#### Subscribing to a presence channel

:::warning Requires Presence
This method requires that the Presence add-on is [enabled](https://support.pubnub.com/hc/en-us/articles/360051974791-How-do-I-enable-add-on-features-for-my-keys-) for your key in the [Admin Portal](https://admin.pubnub.com/). For information on how to receive presence events and what those events are, refer to [Presence Events](https://www.pubnub.com/docs/general/presence/presence-events#subscribe-to-presence-channel).
:::

For any given channel there is an associated Presence channel. You can subscribe directly to the channel by appending `-pnpres` to the channel name. For example the channel named `my_channel` would have the presence channel named `my_channel-pnpres`. Presence data can be observed inside the `SubscribeCallback#message(PubNub, PNMessageResult)` callback.

```dart
var subscription =
    pubnub.subscribe(channels: {'my_channel'}, withPresence: true);
```

### Sample Responses

#### Join event

```json
{
    "Event": "join",
    "Uuid": "175c2c67-uuid-uuid-8f4b-1db94f90e39e",
    "Timestamp": 1345546797,
    "Occupancy": 2,
    "State": null,
    "Channel":" my_channel",
    "Subscription": "",
    "Timetoken": 15034141109823424,
    "UserMetadata": null,
    "Join": null,
    "Timeout": null,
    "Leave": null,
    "HereNowRefresh": false
}
```

#### Leave event

```json
{
    "Event": "leave",
    "Uuid": "175c2c67-uuid-uuid-8f4b-1db94f90e39e",
    "Timestamp": 1345546797,
    "Occupancy": 1,
    "State": null,
    "Channel": "my_channel",
    "Subscription": "",
    "Timetoken": 15034141109823424,
    "UserMetadata": null,
    "Join": null,
    "Timeout": null,
    "Leave": null,
    "HereNowRefresh": false
}
```

#### Timeout event

```json
{
  "Event": "timeout",
  "Uuid": "175c2c67-uuid-uuid-8f4b-1db94f90e39e",
  "Timestamp": 1345546797,
  "Occupancy": 0,
  "State": null,
  "Channel": "my_channel",
  "Subscription": "",
  "Timetoken": 15034141109823424,
  "UserMetadata": null,
  "Join": null,
  "Timeout": null,
  "Leave": null,
  "HereNowRefresh": false
} 
```

#### State change event

```json
{
  "Event":  "state-change",
  "Uuid":  "175c2c67-uuid-uuid-8f4b-1db94f90e39e",
  "Timestamp": 1345546797,
  "Occupancy": 1,
  "State": {
      "isTyping": true
  },
  "Channel":  "my_channel",
  "Subscription":  "",
  "Timetoken": 15034141109823424,
  "UserMetadata": null,
  "Join": null,
  "Timeout": null,
  "Leave": null,
  "HereNowRefresh": false
}
```

#### Interval event

```json
{
    "Event": "interval",
    "Uuid": "175c2c67-uuid-uuid-8f4b-1db94f90e39e",
    "Timestamp": 1345546797,
    "Occupancy": 2,
    "State": null,
    "Channel": "my_channel",
    "Subscription": "",
    "Timetoken": 15034141109823424,
    "UserMetadata": null,
    "Join": null,
    "Timeout": null,
    "Leave": null,
    "HereNowRefresh": false
}
```

When a channel is in interval mode with `presence_deltas` `pnconfig` flag enabled, the interval message may also include the following fields which contain an array of changed UUIDs since the last interval message. This settings can be altered in the [Admin Portal](https://admin.pubnub.com).

* joined
* left
* timed out

For example, this interval message indicates there were 2 new UUIDs that joined and 1 timed out UUID since the last interval:

```json
{
    "Event": "interval",
    "Uuid": "175c2c67-uuid-uuid-8f4b-1db94f90e39e",
    "Timestamp": <unix timestamp>,
    "Occupancy": <# users in channel>,
    "State": null,
    "Channel": "my_channel",
    "Subscription": "",
    "Timetoken": 15034141109823424,
    "UserMetadata": null,
    "Join": ["uuid2", "uuid3"],
    "Timeout": ["uuid1"],
    "Leave": null,
    "HereNowRefresh": false
}
```

If the full interval message is greater than `30 KiB` (since the max publish payload is `∼32 KiB`), none of the extra fields will be present. Instead, there will be a `here_now_refresh` Boolean field set to `true`. This indicates to the user that they should do a `hereNow` request to get the complete list of users present in the channel.

```json
{
    "Event": "interval",
    "Uuid": "175c2c67-uuid-uuid-8f4b-1db94f90e39e",
    "Timestamp": <unix timestamp>,
    "Occupancy": <# users in channel>,
    "State": null,
    "Channel": "my_channel",
    "Subscription": "",
    "Timetoken": 15034141109823424,
    "UserMetadata": null,
    "Join": null,
    "Timeout": null,
    "Leave": null,
    "HereNowRefresh": true
}
```

#### Wildcard subscribe to channels

:::note Requires Stream Controller add-on
This method requires that the *Stream Controller* add-on is enabled for your key in the [Admin Portal](https://admin.pubnub.com/) (with Enable Wildcard Subscribe checked). Read the [support page](https://support.pubnub.com/hc/en-us/articles/360051974791-How-do-I-enable-add-on-features-for-my-keys-) on enabling add-on features on your keys.
:::

Wildcard subscribes allow the client to subscribe to multiple channels using wildcard. For example, if you subscribe to `a.*` you will get all messages for `a.b`, `a.c`, `a.x`. The wildcarded `*` portion refers to any portion of the channel string name after the `dot (.)`.

```dart
var subscription = pubnub.subscribe(channels: {'foo.*'});
```

:::note Wildcard grants and revokes
Only one level (`a.*`) of wildcarding is supported. If you grant on `*` or `a.b.*`, the grant will treat `*` or `a.b.*` as a single channel named either `*` or `a.b.*`. The same rule applies to revokes - you can revoke permissions with wildcards from one level deep, like `a.*`. However, you can do that only if you initially used wildcards to grant permissions to `a.*`.
:::

#### Subscribe to a channel group

:::note Requires Stream Controller add-on
This method requires that the *Stream Controller* add-on is enabled for your key in the [Admin Portal](https://admin.pubnub.com/). Read the [support page](https://support.pubnub.com/hc/en-us/articles/360051974791-How-do-I-enable-add-on-features-for-my-keys-) on enabling add-on features on your keys.
:::

```dart
var subscription = pubnub.subscribe(channelGroups: {'cg1'});
```

#### Subscribe to the presence channel of a channel group

:::note Requires Stream Controller add-on
This method requires that the *Stream Controller* add-on is enabled for your key in the [Admin Portal](https://admin.pubnub.com/). Read the [support page](https://support.pubnub.com/hc/en-us/articles/360051974791-How-do-I-enable-add-on-features-for-my-keys-) on enabling add-on features on your keys.
:::

```dart
var subscription =
    pubnub.subscribe(channelGroups: {'cg1', 'cg2'}, withPresence: true);
```

## 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 pattern** - A way to group and analyze channel data to track performance metrics like message counts and user engagement over time with PubNub Insights.
* **Signal** - A non-persistent message limited to 64 bytes designed for high-volume usecases where the the most recent data is relevant, like GPS location updates.