An MQTT topic tree is more than a naming convention. It becomes a routing API, an authorization boundary, an observability dimension, and a long-lived contract between device firmware and backend services.
A weak topic structure works in a demo because every client subscribes to #. It becomes expensive when fleets, tenants, device types, and teams grow.
Start with an MQTT topic naming convention
An MQTT topic naming convention should make the following context easy to determine:
- Who owns the message?
- Which tenant, site, or environment does it belong to?
- Which asset or device produced it?
- Is it telemetry, state, an event, or a command?
- Which consumers should be allowed to see it?
One possible structure is:
{environment}/{tenant}/{site}/{deviceType}/{deviceId}/{channel}
For example:
prod/acme/shanghai/pump/pump-042/telemetry
prod/acme/shanghai/pump/pump-042/state
prod/acme/shanghai/pump/pump-042/command
There is no universal perfect order. Choose the order that makes your most common subscription and authorization filters narrow and understandable.
Apply the convention consistently and review examples before firmware ships:
| Weak topic | Better topic | Why it is better |
|---|---|---|
sensor1/temp | prod/acme/shanghai/sensor/device-042/telemetry | Adds stable ownership, environment, and device context |
factory/# | prod/acme/shanghai/+/+/telemetry | Narrows the subscription to one site and channel |
device-042/temperature/22.4 | prod/acme/shanghai/sensor/device-042/telemetry | Keeps changing data in the payload |
alice-phone/status | prod/acme/mobile/client-7f3a/state | Removes personal data and uses a stable identifier |
Keep identity stable
Topic levels should use stable machine identifiers. A display name such as "Boiler Room Pump" can change, contain spaces, or be translated. Put display labels in device metadata or payloads.
Do not use a network address as identity. IP addresses, cellular identifiers, and gateway routes can change during the device lifecycle.
Separate message direction and meaning
Mixing telemetry and commands under one undifferentiated topic makes permissions hard to review.
A clear pattern separates channels:
.../{deviceId}/telemetry
.../{deviceId}/state
.../{deviceId}/event
.../{deviceId}/command
.../{deviceId}/command-result
Field devices can publish telemetry, state, events, and command results. They can subscribe only to their command topic. Backend services receive broader read access and carefully scoped command write access.
Treat wildcards as permissions
The + and # wildcards are powerful because one subscription can represent a whole device group:
prod/acme/shanghai/pump/+/telemetry
But the same power can cross ownership boundaries:
prod/#
Grant broad multi-level subscriptions only to trusted services that genuinely need them. A field device should rarely receive wildcard access outside its own identity.
Test policies with negative cases:
- Can device A publish as device B?
- Can a sensor publish to a command topic?
- Can one tenant subscribe to another tenant?
- Can a wildcard escape the intended prefix?
Keep payload schema out of the topic
Topics should route messages, not encode every field. Avoid structures such as:
device-042/temperature/22.4/celsius/battery/91
Use a stable topic and structured payload:
prod/acme/shanghai/sensor/device-042/telemetry
{
"schemaVersion": 2,
"temperature": 22.4,
"temperatureUnit": "C",
"batteryPercent": 91,
"observedAt": "2026-07-27T11:04:00Z"
}
This keeps authorization stable when the payload evolves.
Version breaking changes deliberately
Do not add v1 to every topic by reflex. Many payload changes are backward compatible and can be handled through a schema version in the payload.
Version the topic when routing or permission meaning changes:
prod/acme/v2/shanghai/pump/pump-042/event
Plan a transition where publishers may emit both versions and consumers can move independently. Document the retirement date and monitor subscriptions to the old branch.
Define conventions for retained state and commands
Document whether each channel:
- allows retained messages;
- uses QoS 0 or 1;
- has a maximum payload size;
- includes an expiry interval;
- requires an operation ID;
- emits a result or acknowledgment;
- contains personal or regulated data.
Commands should almost always include expiry and idempotency information. A device reconnecting after several days must not execute a stale operation simply because it was queued.
A review checklist
Before freezing a topic contract:
- Write the five most common publish and subscription examples.
- Write the policy for one device, one gateway, and one backend service.
- Add negative authorization tests.
- Check whether any level exposes secrets or personal data.
- Confirm identifiers remain stable through device replacement and site moves.
- Define payload schema ownership and compatibility.
- Set retained-message, QoS, expiry, and queue rules per channel.
- Record how breaking changes will be introduced and removed.
RunMQTT models publish and subscribe filters as reusable policy templates so a reviewed contract can be assigned to a device family. See the MQTT security guide for the surrounding identity and authorization model.
For the wider production contract around QoS, retries, queues, and observability, continue with the MQTT best practices checklist.
