Industrial IoT

Designing ISA-95 Based MQTT Topics

How to encode the ISA-95 equipment hierarchy into MQTT topics: level order, leaf design, wildcard planning, payload choices and the common pitfalls, plus how Sparkplug B data can be republished into the same tree.

Diagram of isa-95 mqtt topics mapping enterprise, site, area, line and machine levels onto a topic path
Run this on your own MQTT brokerGet a private, managed MQTT broker on the Free plan and start from ready-made code samples.
Try it free Code samples No card required

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

  1. Leading slash. /acme/dallas creates an empty first level and splits subscriptions.
  2. Mixed case. Topics are case sensitive; Line-02 and line-02 are different branches.
  3. Protocol or vendor names in the path. acme/dallas/opcua/... couples consumers to today's integration technology.
  4. Variable depth. Skipping a level for one machine breaks positional wildcards. Use a placeholder work center instead.
  5. Timestamps or IDs in topics. Topics like .../telemetry/20261016 create unbounded branches and retained-message clutter.
  6. Retaining events. Retain state, not event streams; a new subscriber should not receive a stale "batch complete".
  7. Commands without request and response. Use MQTT 5 response topics and correlation data for cmd, so the requester knows whether the command succeeded.
  8. 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.

Related articles

IIoT Edge Gateway: What It Does and How to Choose One

An IIoT edge gateway connects machines that speak Modbus, OPC UA or proprietary protocols to MQTT and the cloud. Learn its core roles, common architectures and how to choose the right one.

7 min read

All articles →

Your private MQTT broker is a few clicks away

Create an account, get your broker credentials and connect your first device — no servers to install, patch or scale.