MQTT user properties are key/value string pairs that you attach to an MQTT 5 message, much like HTTP headers. They let you send metadata such as a trace ID, a schema version or the unit of a measurement without changing the payload format your consumers already parse. In this article you will learn how user properties behave, where they genuinely help, and how to publish and read them in Python, Node.js and the command line.
What MQTT User Properties Are
MQTT 3.1.1 had no place for metadata. Anything beyond the topic and the payload had to be squeezed into the payload itself or encoded in the topic name. MQTT 5 introduced properties on most packets, and the User Property is the one left open for applications.
Key facts about user properties:
- Each property is a pair of UTF-8 strings: a name and a value.
- A message can carry any number of them, and the same name may appear more than once.
- Order is preserved. The broker forwards them in the order they were sent.
- On PUBLISH packets, the broker passes them to subscribers unchanged.
- They count toward the packet size, and therefore toward any message size limit.
- They can also appear on CONNECT, SUBSCRIBE, DISCONNECT and other packets, though PUBLISH is where applications use them most.
Because the broker does not interpret them, user properties are purely a contract between your publishers and subscribers. They are one of several MQTT 5 features that make the protocol friendlier for larger systems.
Common Use Cases for MQTT User Properties
| Use case | Example property | Why not in the payload |
|---|---|---|
| Distributed tracing | traceparent=00-4bf9...-01 |
Tracing is cross-cutting; payload schemas should not change for it |
| Schema versioning | schema=telemetry.v3 |
Consumers pick a parser before decoding |
| Content metadata | unit=celsius, encoding=gzip |
Describes how to read the payload |
| Source identification | source=gateway-12, fw=2.4.1 |
Useful for debugging without touching data |
| Routing hints | tenant=plant-a |
Lets middleware filter without parsing |
| Test markers | synthetic=true |
Lets consumers drop test traffic |
The common thread: information about the message, not the measurement itself. If a dashboard needs a value to draw a chart, it belongs in the payload. If a value tells software how to handle the message, a user property is a good fit.
For binary payloads such as Protobuf, user properties are especially handy because you can add metadata without changing the .proto file. See MQTT payload formats for that comparison.
Sending User Properties from the Command Line
The Mosquitto clients support user properties when you select MQTT 5. We will use the public MQTT test broker at broker.connectmq.com with a unique topic prefix.
Subscribe first. The %P format specifier prints user properties:
mosquitto_sub -V mqttv5 -h broker.connectmq.com -p 1883 -i up-sub-w2c5 \
-t 'connectmq/demo/w2c5/#' -F '%t %p | props: %P'
Then publish with two properties:
mosquitto_pub -V mqttv5 -h broker.connectmq.com -p 1883 -i up-pub-w2c5 \
-t connectmq/demo/w2c5/line1/temp -m '21.7' \
-D publish user-property unit celsius \
-D publish user-property schema temp.v1
The subscriber prints the topic, payload and unit:celsius schema:temp.v1. Publish the same command with -V mqttv311 and the subscriber still gets the payload, but there are no properties, because MQTT 3.1.1 cannot carry them.
Publishing and Reading User Properties in Python
In paho-mqtt 2.x, user properties are a list of tuples on a Properties object. A list rather than a dict, because names may repeat:
import time
import uuid
import paho.mqtt.client as mqtt
from paho.mqtt.properties import Properties
from paho.mqtt.packettypes import PacketTypes
BROKER = "broker.connectmq.com"
TOPIC = "connectmq/demo/w2c5/line1/pressure"
def on_connect(client, userdata, flags, reason_code, properties):
client.subscribe(TOPIC, qos=1)
def on_subscribe(client, userdata, mid, reason_code_list, properties):
props = Properties(PacketTypes.PUBLISH)
props.UserProperty = [
("trace-id", uuid.uuid4().hex),
("unit", "bar"),
("source", "gateway-12"),
]
client.publish(TOPIC, "2.31", qos=1, properties=props)
def on_message(client, userdata, msg):
user_props = dict(getattr(msg.properties, "UserProperty", []))
print(msg.topic, msg.payload.decode(), user_props)
client = mqtt.Client(
mqtt.CallbackAPIVersion.VERSION2,
client_id=f"up-py-{uuid.uuid4().hex[:8]}",
protocol=mqtt.MQTTv5,
)
client.on_connect = on_connect
client.on_subscribe = on_subscribe
client.on_message = on_message
client.connect(BROKER, 1883, keepalive=30)
client.loop_start()
time.sleep(3)
client.loop_stop()
client.disconnect()
Converting to a dict is convenient, but only if you know your names are unique. If you allow repeated names, keep the list.
User Properties in Node.js
MQTT.js v5 exposes user properties as a plain object in properties.userProperties. A value may be a string or an array of strings for repeated names:
const mqtt = require("mqtt");
const client = mqtt.connect("mqtts://broker.connectmq.com:8883", {
clientId: "up-js-" + Math.random().toString(16).slice(2, 10),
protocolVersion: 5,
});
const topic = "connectmq/demo/w2c5/line1/flow";
client.on("connect", () => {
client.subscribe(topic, { qos: 1 }, () => {
client.publish(topic, "12.4", {
qos: 1,
properties: {
userProperties: { unit: "m3/h", tag: ["calibrated", "line1"] },
},
});
});
});
client.on("message", (t, payload, packet) => {
console.log(t, payload.toString(), packet.properties?.userProperties);
client.end();
});
User Properties vs Topic Levels vs Payload Fields
There are three places to put a piece of information in an MQTT message, and each has a different cost:
| Location | Good for | Visible to | Cost |
|---|---|---|---|
| Topic level | Routing and subscription filtering (site, line, device) | Broker, every subscriber, wildcards | Part of every topic string; changes break subscriptions |
| User property | Handling instructions, tracing, schema version | Subscribers only; broker ignores it | Bytes per message; lost on MQTT 3.1.1 hops |
| Payload field | Business data that consumers store or display | Subscribers after decoding | Requires schema changes to add |
A useful rule of thumb: if a subscriber needs to filter on it, use the topic. If a subscriber needs to decide how to process the message before decoding, use a user property. If the subscriber stores or displays it, put it in the payload.
For example, a unit value could go in either a property or the payload. If every consumer stores the unit alongside the value, put it in the payload so it survives being written to a database. If the unit is fixed per topic and only a validation layer checks it, a property is cleaner. Note that wildcard subscriptions only filter on topic levels: a subscriber cannot ask the broker for "messages where tenant equals plant-a" based on a user property.
Design Guidelines
- Define a small vocabulary. Agree on a handful of names across teams and document them. Ad hoc properties quickly become noise.
- Keep them short. Properties travel with every message. Ten long headers on a 20-byte sensor reading can multiply its size.
- Do not put secrets in them. They are as visible as the payload to anyone who can subscribe.
- Do not depend on them for critical data if some consumers still use MQTT 3.1.1. Those consumers will silently lose them.
- Use standard formats where they exist, such as W3C
traceparentfor tracing, so tools can understand them. - Prefer content type for the payload type. MQTT 5 has a dedicated Content Type property and a Payload Format Indicator; use those instead of a custom
content-typeuser property.
Troubleshooting User Properties
Properties are missing on the subscriber. The subscriber or publisher connected with MQTT 3.1.1. Both connections must be MQTT 5.
Messages are rejected as too large. Properties count toward the packet size. On a size-limited plan or broker, trim them. Our article on the MQTT message size limit explains how the limit is enforced.
Values appear as arrays unexpectedly. In MQTT.js a repeated name becomes an array. Handle both shapes or avoid repeating names.
Bridged or republished messages lose properties. Any component that reads and republishes a message must copy the properties explicitly; many simple scripts do not.
User Properties on ConnectMQ
ConnectMQ forwards MQTT 5 user properties unchanged between publishers and subscribers, like any standards-compliant broker. Remember that they count toward the message size: 256 KB on the free Serverless plan, 1 MB on the paid Serverless plan, and 64 KB on the public test broker. To see your metadata in practice, subscribe with the Mosquitto command above or with your own client against your broker, using the credentials from the portal. If you are still choosing infrastructure, our overview of a fully managed MQTT service explains what you get, and the MQTT tutorial covers the fundamentals.
Get Started with ConnectMQ
Sign up at app.connectmq.com and create a broker on the free Serverless plan, no credit card required. Connect with MQTT 5, add a trace ID to your messages, and follow them from device to dashboard on your own cloud MQTT broker.
Frequently Asked Questions
Can user property names repeat?
Yes. MQTT 5 allows the same name more than once, and the broker preserves the order. Store them as a list if you rely on that.
Does the broker change user properties?
On PUBLISH packets the broker forwards them unchanged to subscribers. It does not interpret or filter them.
Are user properties available in MQTT 3.1.1?
No. They are an MQTT 5 feature. A 3.1.1 subscriber receives the payload without any properties.
Do user properties count toward message size?
Yes. They are part of the packet, so they count toward the broker's maximum packet size.



