In MQTT, the topic is the only thing that connects publishers and subscribers. There are no queues or exchanges to configure — the broker simply matches each message's topic against every subscription filter. That makes topic design the backbone of your system: it determines what clients can subscribe to, how permissions are expressed and how easy it is to add new consumers later.
Topic basics
A topic name is a UTF-8 string made of levels separated by a forward slash:
building-a/floor-3/room-12/temperature
vehicles/truck-0042/gps
acme/devices/sensor-17/status- Topics are case-sensitive.
Home/Kitchenandhome/kitchenare different topics. - No registration needed. A topic exists the moment someone publishes to it.
- Every character counts. A leading slash creates an empty first level, so
/home/kitchenis not the same ashome/kitchen. Spaces are legal but cause endless confusion. - Size. Topic names must be at least one character and at most 65,535 bytes, and must not contain the null character.
Wildcards: + and #
Subscribers use topic filters, which may contain two wildcards. Publishers always use concrete topic names — wildcards in a PUBLISH are a protocol violation.
Single-level wildcard: +
+ matches exactly one level. It must occupy a whole level (sensor+ is invalid) but can appear anywhere and more than once.
| Filter | Matches | Does not match |
|---|---|---|
home/+/temperature | home/kitchen/temperature | home/kitchen/oven/temperature |
+/+/status | acme/sensor-17/status | acme/status |
home/+ | home/kitchen | home, home/kitchen/light |
Multi-level wildcard: #
# matches any number of levels — including zero — and must be the last character of the filter, on its own level. home/# matches home/kitchen, home/kitchen/light/state and also the parent home itself. Filters like home/#/light or home# are invalid.
A lone # subscribes to every topic on the broker (except $ topics). It is useful for debugging your own private broker, but on a public broker it floods you with other people's traffic.
$SYS and other $ topics
Topics beginning with $ are reserved for the broker. The most common is $SYS/, where many brokers (Mosquitto, EMQX and others) publish statistics such as connected clients, message rates and uptime. The exact topics are broker-specific — the spec only reserves the prefix.
Wildcards at the first level do not match $ topics: # and +/monitor will never deliver $SYS/... messages. Subscribe explicitly:
mosquitto_sub -h test.mosquitto.org -t '$SYS/broker/clients/connected' -vMQTT 5 adds another reserved prefix: shared subscriptions use $share/<group>/<filter>. Every subscriber in the same group shares the stream, with each message going to only one member — a simple way to load-balance consumers. See MQTT 5 vs 3.1.1 for other MQTT 5 additions such as topic aliases.
Designing a topic hierarchy
A good hierarchy goes from general to specific, so that wildcards at the end select meaningful groups. A common pattern is:
<org-or-app>/<site>/<device-type>/<device-id>/<measurement-or-channel>
acme/berlin/thermostat/th-0193/temperature
acme/berlin/thermostat/th-0193/setpoint/set
acme/berlin/thermostat/th-0193/statusWith this layout:
acme/berlin/#gives a site dashboard everything in Berlin.acme/+/thermostat/+/temperaturegives an analytics service every thermostat reading.acme/berlin/thermostat/th-0193/#gives a single device's full stream.
Best practices
- Don't start with a slash. It adds a pointless empty level.
- Use lowercase, ASCII and hyphens. Avoid spaces and mixed case; they produce subtle mismatches.
- Put the device or client ID in the topic. It makes per-client access control easy (many brokers can substitute the client ID or username into ACL patterns).
- Separate data, commands and state. For example
.../temperaturefor telemetry,.../setpoint/setfor commands and.../statusfor retained state or Last Will presence. - Keep topics short but descriptive. The topic is sent with every message (unless you use MQTT 5 topic aliases), so very long names cost bandwidth on constrained links.
- Don't encode data in the topic. Values belong in the payload; a topic per value creates unbounded topic counts and lots of retained messages.
- Version your schema if it may change. A prefix like
v1/lets old and new devices coexist during a migration. - Avoid broad subscriptions in production. Subscribing to
#in a backend service makes every message on the broker its problem.
Common topic mistakes
- Expecting
a/+to matcha. It does not —+requires a level to be present. Usea/#if you also want the parent. - Trailing slashes.
home/kitchen/has an empty last level and is a different topic fromhome/kitchen. - Overlapping subscriptions. If a client subscribes to both
home/#andhome/+/temperature, the spec lets the broker deliver either a single copy or one copy per matching subscription, so some brokers will hand you duplicates. Keep subscriptions disjoint where you can. - Mixing ID formats. If some devices publish as
Sensor_17and others assensor-17, every filter and ACL rule has to handle both. Normalise IDs at provisioning time. - Treating topics as a database. Brokers are optimised for routing, not querying. If you need history, have a consumer subscribe and write messages to a database.
Topics and access control
Brokers authorise publish and subscribe operations per topic, so your hierarchy doubles as your permission model. If each device writes only under acme/<site>/<type>/<device-id>/, you can grant it exactly that subtree and nothing else. If topics are flat or inconsistent, writing tight rules becomes impossible and teams end up granting # to everyone.
Try it yourself
Open the online MQTT client, subscribe to testmqtt/demo/+/temperature and publish to testmqtt/demo/kitchen/temperature and testmqtt/demo/kitchen/oven/temperature. Only the first message arrives — exactly what the + rule predicts.
Try it in the online MQTT client
Frequently asked questions
What is the difference between + and # in MQTT?
The + wildcard matches exactly one topic level and can appear anywhere in a filter. The # wildcard matches any number of levels, including zero, and must be the last character of the filter.
Can I publish to a topic that contains a wildcard?
No. Wildcards are only valid in subscription filters. A PUBLISH with + or # in the topic name is a protocol error and the broker will close the connection.
Does subscribing to # include $SYS topics?
No. Filters that start with a wildcard do not match topics beginning with $. To receive broker statistics you must subscribe explicitly, for example to $SYS/#.