A good UNS topic structure is what makes a unified namespace navigable: anyone who knows the plant can guess where a value lives without opening documentation. This guide gives you ready-to-use topic trees for discrete and process manufacturing, the naming and payload rules that keep them clean, and a small validator you can run in CI or at the gateway to stop bad topics before they reach the broker.
The Principles Behind a UNS Topic Structure
Every example in this article follows the same five principles. If you adapt the trees, keep the principles.
- Model the plant, not the technology. Topics describe where something is and what it is, not which protocol or PLC brand produced it.
opcua/ands7/do not belong in the path. - Hierarchy first, ISA-95 as the backbone. Enterprise, site, area, line or cell, and equipment give a structure operations already understand. The ISA-95 equipment hierarchy guide explains each level.
- Fixed depth where possible. When every machine sits at the same level, wildcard subscriptions like
+/+/+/+/telemetrywork across the whole enterprise. - A small set of leaf types. End each equipment branch in a predictable set of namespaces such as
telemetry,state,alarms,eventsandcmd. - Stable identifiers. Short, lowercase, hyphenated names that survive reorganisations. Asset numbers are often better than descriptive names that change.
Example 1: Discrete Manufacturing
A discrete plant with assembly lines, machining cells and shared utilities:
acme/ enterprise
dallas/ site
machining/ area
cell-03/ work cell
cnc-01/ equipment
telemetry {"spindle_rpm":..., "load_pct":..., "ts":...}
state "running" | "idle" | "down" (retained)
alarms active alarm list (retained)
events/cycle-complete one message per finished part
cmd/ commands, request/response
robot-01/
telemetry
state
assembly/
line-02/ production line
station-10/
telemetry
events/part-scanned
station-20/
telemetry
line-02/kpi/oee line-level calculated values
utilities/
compressors/
comp-01/telemetry
Notice the line-level kpi branch. Calculated values such as OEE or throughput belong at the level they describe, published by the application that computes them. This keeps raw machine data and derived data clearly separated while staying in the same tree.
Example 2: Process Manufacturing
Process plants think in process cells and units rather than lines and stations. ISA-95 accommodates both, so the tree changes vocabulary but not shape:
acme/
izmir/ site
brewing/ area
brewhouse-1/ process cell
mash-tun/ unit
telemetry {"temp_c":..., "level_pct":..., "ts":...}
state "heating" | "resting" | "transferring"
batch current batch id and recipe (retained)
lauter-tun/
telemetry
fermentation/
fv-07/
telemetry
batch
utilities/
boiler-1/telemetry
water-treatment/ro-1/telemetry
The batch leaf is the process-plant equivalent of a work order: retained, small, and updated when the batch changes. Consumers join batch context with telemetry by topic path, without a separate database lookup.
Example 3: Multi-Site Enterprise With Shared Services
When several sites share a broker or a bridged namespace, add a top-level branch for enterprise systems so that MES, ERP and quality data have a home that does not pretend to be equipment:
acme/dallas/... site trees as above
acme/izmir/...
acme/_enterprise/erp/work-orders/dallas work orders per site (retained)
acme/_enterprise/quality/spc/dallas/line-02
acme/_enterprise/mes/schedule/izmir
The leading underscore keeps the branch sorted apart from site names and makes it obvious that it is not a physical location.
How Sparkplug B Fits a UNS Topic Structure
Sparkplug B uses its own fixed topic format, spBv1.0/<group>/<message type>/<edge node>/<device>. That format is optimised for state management between edge nodes and host applications, not for browsing by plant hierarchy. Our Sparkplug topic namespace article explains it in full.
You do not need to choose. A common pattern keeps Sparkplug for edge-to-host traffic and republishes values into the ISA-95 tree for everyone else. ConnectMQ does this automatically for Sparkplug B traffic: it decodes the Protobuf or JSON payload and republishes into ISA-95 topics, so spBv1.0/factory1/DDATA/line1/cnc1 becomes connectmq/factory1/production/line1/cnc1/telemetry. Dashboards and historians subscribe to readable JSON in the ISA-95 tree, while SCADA hosts keep using native Sparkplug.
Naming and Payload Rules
| Rule | Good | Avoid |
|---|---|---|
| Lowercase only | line-02 |
Line_02, LINE02 |
| Hyphens, no spaces | mash-tun |
mash tun, mash%20tun |
| No leading or trailing slash | acme/dallas/... |
/acme/dallas/ |
No wildcard or $ characters in names |
cnc-01 |
cnc#1, $cnc |
| Units in field names | temp_c, speed_bpm |
temp, speed |
| Timestamps in payloads | "ts": "2026-10-16T08:00:00Z" |
Relying on arrival time |
| Retain current state | state, batch, alarms |
Retaining event streams |
| Version payloads | "v": 2 |
Silent breaking changes |
A leading slash is a classic mistake: it creates an empty first level, so /acme/# and acme/# are different subscriptions. Mixed case is another: MQTT topics are case sensitive, and Line-02 and line-02 become two different branches that quietly split your data.
Validate Topics Before They Reach the Broker
The script below checks topics against the rules above and the five-level equipment structure. Use it in a gateway, in a CI test over your tag mapping file, or as a monitoring subscriber that reports violations.
import re
LEVEL = re.compile(r"^[a-z0-9]+(-[a-z0-9]+)*$")
LEAVES = {"telemetry", "state", "alarms", "batch", "events", "cmd", "kpi"}
def validate(topic: str, depth: int = 5) -> list[str]:
"""Return a list of problems; an empty list means the topic is valid."""
problems = []
if topic.startswith("/") or topic.endswith("/"):
problems.append("leading or trailing slash")
levels = topic.strip("/").split("/")
if any(ch in topic for ch in "+#$"):
problems.append("wildcard or $ character in topic name")
if any(" " in level for level in levels):
problems.append("space in topic level")
if len(levels) < depth + 1:
problems.append(f"expected at least {depth + 1} levels, got {len(levels)}")
return problems
for level in levels[:depth]:
if not (LEVEL.match(level) or level.startswith("_")):
problems.append(f"level '{level}' is not lowercase-hyphenated")
if levels[depth] not in LEAVES:
problems.append(f"leaf '{levels[depth]}' not in {sorted(LEAVES)}")
return problems
if __name__ == "__main__":
tests = [
"acme/dallas/machining/cell-03/cnc-01/telemetry",
"acme/dallas/machining/cell-03/CNC_01/telemetry",
"/acme/dallas/assembly/line-02/station-10/telemetry",
"acme/dallas/assembly/line-02/station-10/temperature",
]
for t in tests:
result = validate(t)
print("OK " if not result else "FAIL", t, "" if not result else result)
Run it with python validate_topics.py and adjust LEAVES and depth to match your standard.
Evolving the Structure Without Breaking Consumers
No topic structure survives contact with a real plant unchanged. Lines are rebuilt, machines move between cells, a new site is acquired with its own naming habits. The goal is not to avoid change but to make it safe.
- Add, never rename in place. When a branch must change, publish to the new path and the old path in parallel for a defined period, then retire the old one. Announce the sunset date to every consumer owner.
- Clear retained messages on retirement. A retired branch still returns retained values to new subscribers. Publish an empty retained message to each old state topic to remove it.
- Keep a registry. A simple file in version control listing every branch, its owner, its leaf types and its payload schema version is enough. The validator above can read its rules from that file.
- Version payloads, not topics. Put a
vfield in the payload when the schema changes. Topics with/v2/in the path tend to multiply and confuse wildcard subscriptions. - Review new branches. A short review by the namespace owner before a gateway goes live catches most problems while they are still cheap to fix.
Treat the structure like an API that the whole plant depends on, because that is exactly what it becomes once dashboards, historians and MES integrations subscribe to it.
Try the Structure on a Live Broker
Before rolling a structure out, publish a few sample messages and browse them. On the public MQTT test broker at broker.connectmq.com you can do this without an account, as long as you use a unique prefix and remember that the public area is readable by everyone. The public broker allows 8 topic levels, so connectmq/demo/<id>/ plus the five-level tree and a leaf fits exactly when you drop the enterprise level:
mosquitto_pub -h broker.connectmq.com -p 1883 -i uns-test-pub-q7 -r \
-t "connectmq/demo/q7w2/dallas/machining/cell-03/cnc-01/state" -m "running"
mosquitto_sub -h broker.connectmq.com -p 1883 -i uns-test-sub-q7 \
-t "connectmq/demo/q7w2/dallas/+/+/+/state" -v
On a private ConnectMQ broker you can use the full tree without the demo prefix, and the portal's live topic explorer and ISA-95 devices view show at a glance whether devices land at the right enterprise, site, area and line. For step-by-step encoding rules, continue with ISA-95 MQTT topics, and for the full build process see how to build a unified namespace. The wider picture is in our industrial IoT MQTT guide.
Put Your UNS Topic Structure on ConnectMQ
A topic structure is only as good as the broker that carries it and the visibility you have into it. ConnectMQ is a fully managed MQTT service that detects ISA-95 and unified namespace topics, decodes Sparkplug B and republishes it into your ISA-95 tree. Create a broker on the free Serverless plan at app.connectmq.com, no credit card required, and publish your first namespace branch in minutes.
Frequently Asked Questions
How many levels should a UNS topic structure have?
Most plants use five or six: enterprise, site, area, line or cell, equipment, and a leaf such as telemetry or state. Fixed depth keeps wildcard subscriptions simple.
Should each metric get its own topic?
Both approaches work. One JSON object per equipment telemetry topic is simpler and cheaper; a topic per metric suits tools that map topics directly to tags. Pick one per leaf type and apply it consistently.
Where do calculated KPIs belong?
At the level they describe, published by the application that computes them, for example a kpi/oee branch under the line.
Can Sparkplug B topics be part of a UNS?
Yes, but they are not organised by plant hierarchy. Republish Sparkplug values into ISA-95 topics so all consumers can browse one tree.



