ISA-95 MQTT topics put the plant's equipment hierarchy directly into the topic path, so acme/dallas/packaging/line-02/filler/telemetry tells every subscriber exactly where a value comes from. In this guide you will learn how to encode each ISA-95 level, how to design the leaf of the topic, how to plan wildcard subscriptions before you need them, and which pitfalls cause the most rework once a namespace is in production.
Why Encode ISA-95 Into MQTT Topics
MQTT topics are free-form strings separated by slashes, and the MQTT broker attaches no meaning to them beyond matching subscriptions. That freedom is dangerous at plant scale. If each integrator invents a structure, consumers end up with a lookup table per source.
The ISA-95 equipment hierarchy solves this by providing a structure that already exists in the plant's documentation, MES and maintenance system. Encoding it in topics gives you three properties for free:
- Discoverability. Anyone who knows the plant can find a value by navigating the tree.
- Context. The topic carries the asset address, so payloads do not need to repeat it.
- Selective subscription. Wildcards map naturally to organisational scopes: one site, one area, every filler.
If you need a refresher on the levels themselves, read the ISA-95 equipment hierarchy first.
The Basic Pattern
The canonical pattern places levels from broadest to narrowest, followed by a namespace that says what kind of data the message contains:
<enterprise>/<site>/<area>/<work center>/<work unit>/<namespace>[/<detail>]
acme/dallas/packaging/line-02/filler/telemetry
acme/dallas/packaging/line-02/filler/state
acme/dallas/packaging/line-02/filler/alarms
acme/dallas/packaging/line-02/filler/events/batch-complete
acme/dallas/packaging/line-02/filler/cmd/stop
Rules for Each Level
| Level | Rule | Example |
|---|---|---|
| Enterprise | One stable short name; omit only if the broker serves a single company | acme |
| Site | Location code that survives reorganisations | dallas, izmir |
| Area | Function or product area | packaging, utilities |
| Work center | Line, cell, process cell, unit or storage zone | line-02, cell-03 |
| Work unit | Machine or unit, ideally by asset tag | filler, cnc-01 |
| Namespace | Fixed vocabulary | telemetry, state, alarms, events, cmd |
Keep the depth fixed for equipment topics. When every work unit sits at level five, +/+/+/+/+/state returns every machine state in the enterprise, and the position of each level in the path is guaranteed.
Designing the Leaf
The namespace and anything after it is where most design debates happen. There are two mainstream options for telemetry.
One object per equipment. All current values of a machine go into a single JSON document on .../filler/telemetry. This is compact, keeps related values consistent in time, and needs only one retained message per machine.
{ "ts": "2026-10-16T08:15:02Z", "speed_bpm": 118.4, "temp_c": 41.2, "good_count": 18342, "v": 1 }
One topic per metric. Each value has its own topic, such as .../filler/telemetry/speed_bpm, with a scalar or small JSON payload. This maps naturally to tag-based tools like SCADA systems, and consumers can subscribe to exactly the signals they need.
Both are valid. Choose per namespace and document it. A common hybrid is an object for telemetry and per-topic retained values for state and a few key KPIs.
Plan Your Wildcard Subscriptions
Before you finalise the structure, write down the subscriptions your consumers will need and check that each one is a single wildcard filter:
| Consumer | Needs | Subscription |
|---|---|---|
| Site dashboard | Everything at one site | acme/dallas/# |
| Packaging supervisor | All states in one area | acme/dallas/packaging/+/+/state |
| Fleet analytics | Every filler, everywhere | acme/+/+/+/filler/telemetry |
| Alarm service | All alarms | acme/+/+/+/+/alarms |
| Historian | All telemetry | acme/+/+/+/+/telemetry |
If a needed scope requires several subscriptions or client-side filtering, the structure is fighting you. Usually it means a level is missing, inconsistently ordered, or the leaf vocabulary is too loose.
Publishing ISA-95 Topics From Code
Generating topics in one place prevents typos from creating rogue branches. This Python helper builds and validates the path, then publishes retained state and telemetry. Install the client with pip install "paho-mqtt>=2.0". It uses the public broker with a demo prefix; on a private broker set PREFIX to your enterprise name.
import json
import re
import threading
from dataclasses import dataclass
from datetime import datetime, timezone
import paho.mqtt.client as mqtt
PREFIX = "connectmq/demo/r8t5" # on your own broker: "acme"
SAFE = re.compile(r"^[a-z0-9]+(-[a-z0-9]+)*$")
NAMESPACES = {"telemetry", "state", "alarms", "events", "cmd"}
@dataclass(frozen=True)
class Asset:
site: str
area: str
work_center: str
work_unit: str
def topic(self, namespace: str) -> str:
for level in (self.site, self.area, self.work_center, self.work_unit):
if not SAFE.match(level):
raise ValueError(f"invalid level: {level!r}")
if namespace not in NAMESPACES:
raise ValueError(f"unknown namespace: {namespace!r}")
return f"{PREFIX}/{self.site}/{self.area}/{self.work_center}/{self.work_unit}/{namespace}"
filler = Asset("dallas", "packaging", "line-02", "filler")
connected = threading.Event()
def on_connect(client, userdata, flags, reason_code, properties):
print("connected:", reason_code)
connected.set()
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="isa95-pub-r8t5")
client.on_connect = on_connect
client.connect("broker.connectmq.com", 1883, keepalive=60)
client.loop_start()
connected.wait(timeout=10)
client.publish(filler.topic("state"), "running", qos=1, retain=True).wait_for_publish()
telemetry = {"ts": datetime.now(timezone.utc).isoformat(), "speed_bpm": 118.4, "temp_c": 41.2, "v": 1}
client.publish(filler.topic("telemetry"), json.dumps(telemetry), qos=1, retain=True).wait_for_publish()
print("published to", filler.topic("telemetry"))
client.loop_stop()
client.disconnect()
Verify with a wildcard that matches the table above:
mosquitto_sub -h broker.connectmq.com -p 1883 -i isa95-sub-r8t5 -t "connectmq/demo/r8t5/+/packaging/+/+/state" -v
The public MQTT test broker is readable by anyone and allows 8 topic levels, which is why the demo prefix replaces the enterprise level here. Keep real asset names and values for a private broker.
Bringing Sparkplug B Into the ISA-95 Tree
Sparkplug B topics follow a fixed format, spBv1.0/<group>/<type>/<edge node>/<device>, with Protobuf payloads. They do not follow ISA-95, and consumers that are not Sparkplug-aware cannot read them. Rather than abandoning Sparkplug at the edge, republish its data into your ISA-95 tree.
ConnectMQ does this automatically: it decodes Sparkplug B payloads, Protobuf or JSON, and republishes them into ISA-95 topics. For example, spBv1.0/factory1/DDATA/line1/cnc1 is republished as connectmq/factory1/production/line1/cnc1/telemetry. Choose Sparkplug group and edge node IDs that carry meaningful plant names so the resulting ISA-95 paths are readable. The trade-offs between the two approaches are discussed in MQTT, Sparkplug B and the unified namespace.
Pitfalls to Avoid
- Leading slash.
/acme/dallascreates an empty first level and splits subscriptions. - Mixed case. Topics are case sensitive;
Line-02andline-02are different branches. - Protocol or vendor names in the path.
acme/dallas/opcua/...couples consumers to today's integration technology. - Variable depth. Skipping a level for one machine breaks positional wildcards. Use a placeholder work center instead.
- Timestamps or IDs in topics. Topics like
.../telemetry/20261016create unbounded branches and retained-message clutter. - Retaining events. Retain state, not event streams; a new subscriber should not receive a stale "batch complete".
- Commands without request and response. Use MQTT 5 response topics and correlation data for
cmd, so the requester knows whether the command succeeded. - Deep paths on constrained links. Long topics add bytes to every message. MQTT 5 topic aliases reduce that overhead without changing the design.
Document the Standard Before Rollout
A topic standard that lives only in one engineer's head will be broken by the second integrator. Before the first gateway goes live, write a one-page standard that covers:
- The level order and the allowed characters for each level.
- The list of namespaces and what each one may contain, with an example payload.
- Which topics are retained and which are not.
- The timestamp format, time zone and units convention.
- Who approves new sites, areas and work centers, and how requests are made.
Hand this page to every system integrator and gateway vendor along with the topic helper above or its equivalent in their language. When the standard and the code agree, most mistakes are caught before they ever reach the broker.
Security deserves its own note: issue one credential per gateway and application so every publisher is identifiable and revocable, and use TLS on port 8883 everywhere outside the lab. Our MQTT security best practices cover the rest.
For complete trees ready to copy, see UNS topic structure. For the architecture around them, the industrial IoT MQTT guide is the place to start.
Run ISA-95 MQTT Topics on ConnectMQ
ConnectMQ is a hosted MQTT broker that understands industrial topic structures: it detects ISA-95 and unified namespace topics, shows devices by ISA-95 level in the portal, and republishes Sparkplug B into your tree. Create a broker on the free Serverless plan at app.connectmq.com, no credit card required, and publish your first ISA-95 branch today.
Frequently Asked Questions
Should the enterprise name be the first topic level?
Yes, if the broker might ever serve more than one company or business unit. For a single-company broker you can omit it, but adding it later means renaming every topic.
How do I handle machines that do not belong to a line?
Give them a placeholder work center such as standalone or shared so they sit at the same depth as everything else.
Can I use spaces or uppercase letters in ISA-95 MQTT topics?
MQTT allows them, but they cause subtle bugs. Use lowercase letters, digits and hyphens only.
Do ISA-95 topics work with MQTT 3.1.1?
Yes. The topic design is version independent. MQTT 5 adds helpful extras such as topic aliases and response topics for commands.



