Send different message types
This guide shows you how to send typed data to a PubNub channel.
- Publish a message and label it with a custom message type, so subscribers can route it without parsing the payload.
- Send a signal instead, when the value changes constantly.
- Attach metadata for server-side filtering.
This page covers customMessageType: a label you set at publish time. Platform-assigned type integers (file events, App Context events, message action events) are set automatically by their respective APIs.
Every call on this page addresses exactly one channel and needs an SDK instance initialized with your publish key. If you don't have a keyset yet, start with Set up your account. If you already publish untyped messages and only need the type label, skip to Label the message with a custom message type.
Examples use the JavaScript, Swift, Java, Kotlin, Python, and PHP SDKs, which document every parameter this guide uses. For any other language, refer to Available SDKs.
Publish a message
Call publish() with a channel and a payload. The payload can be any JSON-serializable value: an object, an array, a string, or a number. Don't serialize it yourself, because the SDK does that for you.
- JavaScript
- Swift
- Java
- Kotlin
- Python
- PHP
1const result = await pubnub.publish({
2 channel: "my_channel",
3 message: { text: "Hello World!" },
4});
5
6console.log("timetoken:", result.timetoken);
1pubnub.publish(
2 channel: "my_channel",
3 message: ["text": "Hello World!"]
4) { result in
5 switch result {
6 case let .success(timetoken):
7 print("timetoken: \(timetoken)")
8 case let .failure(error):
9 print("failed: \(error.localizedDescription)")
10 }
11}
1JsonObject data = new JsonObject();
2data.addProperty("text", "Hello World!");
3
4Channel channel = pubnub.channel("my_channel");
5
6channel.publish(data)
7 .async(result -> result.onSuccess(value ->
8 System.out.println("timetoken: " + value.getTimetoken())));
1val channel = pubnub.channel("my_channel")
2
3channel.publish(
4 message = mapOf("text" to "Hello World!")
5).async { result ->
6 result.onSuccess { println("timetoken: ${it.timetoken}") }
7 .onFailure { println("failed: ${it.message}") }
8}
1def publish_callback(result, status):
2 if status.is_error():
3 print(status.error_data.information)
4 else:
5 print("timetoken: {}".format(result.timetoken))
6
7pubnub.publish() \
8 .channel("my_channel") \
9 .message({"text": "Hello World!"}) \
10 .pn_async(publish_callback)
1$result = $pubnub->publish()
2 ->channel("my_channel")
3 ->message(["text" => "Hello World!"])
4 ->sync();
5
6echo "timetoken: " . $result->getTimetoken();
A successful publish returns [1, "Sent", "<timetoken>"], and a failed one returns 0 in the first position. For what the timetoken identifies afterwards, refer to Publish response and the timetoken.
The standard message payload size limit is 32 KiB. This includes the channel name and any metadata. If your payloads approach that limit, measure one before you ship it.
Need larger messages?
PubNub supports payloads larger than the standard limit, but raising it requires verifying compatibility with your use case.
Talk to our team to discuss increasing the message size limit for your use case.
The sections below build on this call by adding a type label, a signal variant, and metadata.
Label the message with a custom message type
Set customMessageType (custom_message_type in the Python SDK) to attach a business-specific label to a message, a signal, or a file. Subscribers then branch on the label instead of inspecting the payload, and a subscribe filter can discard traffic of the wrong type server-side before it reaches a client.
The custom_message_type value accepted by the PubNub Publish, Signal, and File Sharing APIs must be a case-sensitive alphanumeric string of 3 to 50 characters. Dashes (-) and underscores (_) are allowed. The value cannot start with a special character or with the reserved prefixes pn_ or pn-.
- JavaScript
- Swift
- Java
- Kotlin
- Python
- PHP
1await pubnub.publish({
2 channel: "my_channel",
3 message: { text: "Hello World!" },
4 customMessageType: "text-message",
5});
1pubnub.publish(
2 channel: "my_channel",
3 message: ["text": "Hello World!"],
4 customMessageType: "text-message"
5) { result in
6 switch result {
7 case let .success(timetoken):
8 print("timetoken: \(timetoken)")
9 case let .failure(error):
10 print("failed: \(error.localizedDescription)")
11 }
12}
1JsonObject data = new JsonObject();
2data.addProperty("text", "Hello World!");
3
4Channel channel = pubnub.channel("my_channel");
5
6channel.publish(data)
7 .customMessageType("text-message")
8 .async(result -> { /* check result */ });
1val channel = pubnub.channel("my_channel")
2
3channel.publish(
4 message = mapOf("text" to "Hello World!"),
5 customMessageType = "text-message"
6).async { result -> /* check result */ }
1pubnub.publish() \
2 .channel("my_channel") \
3 .message({"text": "Hello World!"}) \
4 .custom_message_type("text-message") \
5 .pn_async(publish_callback)
1$result = $pubnub->publish()
2 ->channel("my_channel")
3 ->message(["text" => "Hello World!"])
4 ->customMessageType("text-message")
5 ->sync();
Two consequences are worth designing for.
- The label is absent unless a publish set it, so give subscribers a default path for untyped traffic rather than assuming the field is present.
- Messages retrieved from Message Persistence include the custom message type only when the request enables the
include_custom_message_typeflag, whose name varies across SDKs. Enable that flag in any history call whose results your routing logic depends on.
A message also carries a separate integer messageType that PubNub sets, which identifies the kind of event PubNub delivered rather than your business label. For its values and for how both fields appear in subscribe and history payloads, refer to Message types categorize traffic on a shared channel.
Choose a payload shape per type
PubNub validates a payload only for size and JSON-serializability, so a payload shape is a convention your application owns. Choosing one shape per type up front lets a receiver render an event from its type alone. It also lets an older client build recognize a type it doesn't handle, and prompt for an upgrade instead of failing on a shape it can't parse.
The shapes below are examples: an envelope with a content object holding the type-specific fields. Adapt the field names to your application.
| Type | customMessageType | content fields |
|---|---|---|
| Plain text | text-message | message |
| Text in several languages | multi-language-text | message, as an object keyed by language code |
| Text with an image | text-with-image | text, attachments as an array of {"image": {"source": "…"}} |
| Document link | document | link, thumbnail |
| Video link | video | url, thumbnail |
| Chat invitation | chat-invitation | channel, message |
| Video call invitation | video-invitation | session |
| Poll | poll | question, answers as an object of option to count |
| Typing indicator | typing-indicator | event. Send this one as a signal, not a message |
A fully worked payload for text-with-image:
{
"content": {
"text": "The weather is gorgeous today. Lunch at Bob's Diner? 🌞",
"attachments": [
{ "image": { "source": "https://www.pubnub.com/pubnub_logo.svg" } }
]
}
}
Every event already carries the publisher's User ID, so add a sender field only when you need a display name or an identity different from the publishing connection's.
Send a signal instead of a message
Use signal() when a value changes constantly and only the latest one matters: a typing indicator, a live GPS position, or a sensor reading. Signal payloads are limited to 64 bytes. Signals are never stored and can't trigger mobile push notifications, so use a message for anything a client must be able to recover later.
Send signals and messages on separate channels. Mixing them on the same channel interferes with how the SDK recovers missed events after a disconnect.
- JavaScript
- Swift
- Java
- Kotlin
- Python
- PHP
1await pubnub.signal({
2 channel: "locations.route1",
3 message: ["35.9296", "-78.9482"],
4 customMessageType: "gps-update",
5});
1pubnub.signal(
2 channel: "locations.route1",
3 message: ["35.9296", "-78.9482"],
4 customMessageType: "gps-update"
5) { result in
6 switch result {
7 case let .success(timetoken):
8 print("timetoken: \(timetoken)")
9 case let .failure(error):
10 print("failed: \(error.localizedDescription)")
11 }
12}
1Channel channel = pubnub.channel("locations.route1");
2
3channel.signal(Arrays.asList("35.9296", "-78.9482"))
4 .customMessageType("gps-update")
5 .async(result -> { /* check result */ });
1val channel = pubnub.channel("locations.route1")
2
3channel.signal(
4 message = listOf("35.9296", "-78.9482"),
5 customMessageType = "gps-update"
6).async { result -> /* check result */ }
1pubnub.signal() \
2 .channel("locations.route1") \
3 .message(["35.9296", "-78.9482"]) \
4 .custom_message_type("gps-update") \
5 .pn_async(publish_callback)
1// The PHP SDK's signal() takes no customMessageType parameter.
2$result = $pubnub->signal()
3 ->channel("locations.route1")
4 ->message(["35.9296", "-78.9482"])
5 ->sync();
Attach metadata to a message
Pass meta to carry data alongside a message that stays out of the payload. Set it when you want PubNub to filter on a value server-side, or when the payload is encrypted and a value still has to be readable by PubNub services such as Functions and subscribe filtering. Because meta stays unencrypted, never put a secret in it.
- JavaScript
- Swift
- Java
- Kotlin
- Python
- PHP
1await pubnub.publish({
2 channel: "notifications",
3 message: {
4 title: "System maintenance",
5 body: "Scheduled maintenance window starting soon",
6 },
7 customMessageType: "system-notice",
8 meta: { priority: "high", region: "us-west" },
9});
1pubnub.publish(
2 channel: "notifications",
3 message: [
4 "title": "System maintenance",
5 "body": "Scheduled maintenance window starting soon"
6 ],
7 customMessageType: "system-notice",
8 meta: ["priority": "high", "region": "us-west"]
9) { result in
10 switch result {
11 case let .success(timetoken):
12 print("timetoken: \(timetoken)")
13 case let .failure(error):
14 print("failed: \(error.localizedDescription)")
15 }
show all 16 lines1Map<String, Object> message = new HashMap<>();
2message.put("title", "System maintenance");
3message.put("body", "Scheduled maintenance window starting soon");
4
5Map<String, Object> meta = new HashMap<>();
6meta.put("priority", "high");
7meta.put("region", "us-west");
8
9Channel channel = pubnub.channel("notifications");
10
11channel.publish(message)
12 .customMessageType("system-notice")
13 .meta(meta)
14 .async(result -> { /* check result */ });
1val channel = pubnub.channel("notifications")
2
3channel.publish(
4 message = mapOf(
5 "title" to "System maintenance",
6 "body" to "Scheduled maintenance window starting soon"
7 ),
8 meta = mapOf("priority" to "high", "region" to "us-west"),
9 customMessageType = "system-notice"
10).async { result -> /* check result */ }
1pubnub.publish() \
2 .channel("notifications") \
3 .message({
4 "title": "System maintenance",
5 "body": "Scheduled maintenance window starting soon"
6 }) \
7 .meta({"priority": "high", "region": "us-west"}) \
8 .custom_message_type("system-notice") \
9 .pn_async(publish_callback)
1$result = $pubnub->publish()
2 ->channel("notifications")
3 ->message([
4 "title" => "System maintenance",
5 "body" => "Scheduled maintenance window starting soon"
6 ])
7 ->meta(["priority" => "high", "region" => "us-west"])
8 ->customMessageType("system-notice")
9 ->sync();
Metadata values must be JSON-serializable.
meta is a message-only parameter. Signals don't accept it, so a value a filter has to see must travel in the signal payload itself.
Related tasks
- Filter received messages. Write the subscribe filter that reads the type or the metadata you just set.
- Receive messages. Handle the events these calls produce.
- Delete messages. Remove a published message from a channel's history.
- Send a file to a channel. Publish a file with an optional text message and a custom message type.
- Encrypt and decrypt all messages and files. Encrypt the payload while leaving
metafilterable.