Managed MQTT Cloud

MQTT Topic Browser: How to Explore and Debug Live Topic Trees

A hands-on guide to MQTT topic browsers: generate a realistic topic tree, explore it in the ConnectMQ live topic explorer, and use what you see to fix naming, retained-message and payload problems.

MQTT topic browser showing a live hierarchical topic tree with sensors, lines and machines updating in real time
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

An MQTT topic browser shows every topic flowing through a broker as a live, expandable tree, with the latest payload next to each node. In this tutorial you will generate a realistic topic hierarchy, explore it in the ConnectMQ live topic explorer and on the command line, and learn the patterns that reveal naming mistakes, stale retained messages and malformed payloads in seconds.

What an MQTT Topic Browser Does

MQTT has no built-in "list topics" command. Topics are not created or registered; they exist only because someone publishes to them. A topic browser works around this by subscribing to a broad wildcard such as # (or a project root like factory1/#) and building a tree from the topic strings it receives. Each slash becomes a level, each level becomes a folder, and the most recent payload is shown on the leaf.

That simple idea is surprisingly powerful. A flat stream of messages from mosquitto_sub is hard to read once you have more than a dozen devices. A tree tells you at a glance:

  • Which branches exist, and which ones should not.
  • Which leaves are updating right now, and which stopped hours ago.
  • Which values are retained, meaning a new subscriber will receive them immediately.
  • How large and how well-formed the payloads are.

If topic structure is new to you, it helps to understand what an MQTT broker does with topics and subscriptions before you start.

Generate a Topic Tree to Explore

You need traffic to browse. The script below publishes a small factory hierarchy to the free public MQTT broker at broker.connectmq.com. It deliberately includes three common mistakes so you can find them in the browser. Install the client with pip install "paho-mqtt>=2.0".

import json
import random
import time
import uuid

import paho.mqtt.client as mqtt

RUN_ID = uuid.uuid4().hex[:6]
ROOT = f"connectmq/demo/{RUN_ID}"

client = mqtt.Client(
    mqtt.CallbackAPIVersion.VERSION2,
    client_id=f"tree-gen-{RUN_ID}",
)
client.connect("broker.connectmq.com", 1883, keepalive=60)
client.loop_start()
print(f"Publishing under {ROOT}/  (subscribe to {ROOT}/#)")

# Retained device metadata
for line in ("line1", "line2"):
    for machine in ("cnc1", "cnc2"):
        meta = {"vendor": "demo", "model": "X200", "line": line}
        client.publish(f"{ROOT}/factory1/{line}/{machine}/meta",
                       json.dumps(meta), qos=1, retain=True)

# Mistake 1: inconsistent casing creates a parallel branch
client.publish(f"{ROOT}/factory1/Line1/cnc1/meta", "{}", qos=1, retain=True)
# Mistake 2: trailing slash creates an empty last level
client.publish(f"{ROOT}/factory1/line2/cnc2/telemetry/", "42", qos=0)

try:
    while True:
        for line in ("line1", "line2"):
            for machine in ("cnc1", "cnc2"):
                payload = {
                    "spindle_rpm": random.randint(9000, 12000),
                    "temperature": round(random.uniform(35, 60), 1),
                    "ts": int(time.time()),
                }
                client.publish(f"{ROOT}/factory1/{line}/{machine}/telemetry",
                               json.dumps(payload), qos=0)
        # Mistake 3: a payload that is not valid JSON
        client.publish(f"{ROOT}/factory1/line1/cnc2/telemetry",
                       "temp=48.2;rpm=10100", qos=0)
        time.sleep(2)
except KeyboardInterrupt:
    client.loop_stop()
    client.disconnect()

The script publishes four messages per cycle plus the malformed one, which keeps it comfortably inside the public broker's limit of 10 messages per second per client. Leave it running and note the printed root.

Explore the Tree in the ConnectMQ Topic Explorer

On your own broker, the live topic explorer in the ConnectMQ portal is the built-in topic browser. To follow along, create a free broker at the portal, then point the script above at it by switching to port 8883 with TLS and adding the credentials the portal generated (YOUR_USERNAME, YOUR_PASSWORD, YOUR_CLIENT_ID). For a quick look at the public broker instead, any MQTT explorer that supports WebSockets can connect to wss://broker.connectmq.com:8084/mqtt and subscribe to your connectmq/demo/<run-id>/# root.

As messages arrive, walk the tree from the top:

  1. Expand the root. You should see factory1 and, beneath it, line1, line2 and an unexpected Line1. Topics are case-sensitive, so Line1 is a separate branch that no subscriber to line1/# will ever receive.
  2. Open line2/cnc2. Under telemetry there is a child with an empty name. That is the trailing slash: telemetry/ has one more level than telemetry, and a subscription to .../cnc2/telemetry will not match it.
  3. Open line1/cnc2/telemetry. The value flips between JSON and temp=48.2;rpm=10100. Any consumer that parses JSON will throw on every second message.
  4. Look at the meta leaves. They carry the retained flag. Disconnect and reconnect, and they appear immediately, while telemetry only shows up when the next message arrives.

The ConnectMQ portal also offers a devices view organised by ISA-95 levels (enterprise, site, area, line, cell). If your topics follow a Unified Namespace layout, that view groups the same data by plant hierarchy instead of raw strings, which makes misplaced devices stand out. Our article on Sparkplug B and the Unified Namespace explains that structure.

Browse Topics from the Command Line

A graphical browser is not always at hand. mosquitto_sub with the -v flag prints the topic before each payload, and a few extra flags turn it into a lightweight browser:

# Every topic under your root, with topic names
mosquitto_sub -h broker.connectmq.com -p 1883 -i browse-$RANDOM \
  -t 'connectmq/demo/RUN_ID/#' -v

# Only retained messages: connect, print what is retained, exit after 3 s
mosquitto_sub -h broker.connectmq.com -p 1883 -i retained-$RANDOM \
  -t 'connectmq/demo/RUN_ID/#' -v --retained-only -W 3

# Unique topic names seen in 30 seconds
timeout 30 mosquitto_sub -h broker.connectmq.com -p 1883 -i list-$RANDOM \
  -t 'connectmq/demo/RUN_ID/#' -F '%t' | sort -u

Replace RUN_ID with the value the script printed. The last command is the closest thing MQTT has to "list topics", and it is a quick way to diff the topics you expect against the topics that actually exist. More recipes are in our mosquitto_pub and mosquitto_sub examples.

Debugging Patterns a Topic Browser Reveals

Once you are used to reading a tree, most topic problems fall into a small number of patterns.

What you see in the tree What it means Fix
Two branches that differ only by case Inconsistent naming in firmware or config Standardise on lowercase and enforce it in code review
A leaf with an empty name Trailing slash in the publish topic Strip trailing slashes before publishing
A first level with an empty name Leading slash (/factory1/...) Remove the leading slash; it adds a useless level
A leaf whose value never changes Device stopped publishing, or the value is stale retained data Check the device; clear the retained message if obsolete
Values alternating between formats Two firmware versions or two publishers on one topic Version the payload or separate the topics
Hundreds of sibling leaves with IDs in the name Unique IDs placed too high in the hierarchy Move IDs down to the device level
Very deep branches Over-structured topics Flatten; the public broker allows 8 levels

Clearing Stale Retained Messages

Retained values persist on the broker until replaced. A decommissioned device can therefore keep showing a "current" value in every dashboard. To remove a retained message, publish an empty retained payload to the exact topic:

mosquitto_pub -h broker.connectmq.com -p 1883 -i clear-$RANDOM \
  -t 'connectmq/demo/RUN_ID/factory1/Line1/cnc1/meta' -r -n

The -n flag sends a zero-length payload and -r sets the retain flag. Refresh the topic browser and the Line1 branch disappears once it has no other children.

Using a Topic Browser Well in Production

A browser that subscribes to # on a busy production broker receives every message, which costs bandwidth and can hit subscription or connection quotas. A few habits keep it useful:

  • Subscribe to the narrowest root that answers your question, such as factory1/line2/#, rather than #.
  • Use a dedicated credential for debugging tools, so a browser session never takes a device's client ID and knocks the device offline.
  • Check connection state as well as data. The topic $SYS/broker/connection/{clientId}/state on your ConnectMQ broker tells you whether a specific client is connected, which separates "device offline" from "device online but silent".
  • Write down your naming rules and compare the tree against them after every firmware release.

For editor-centric workflows, the ConnectMQ VS Code extension and the ConnectMQ Desktop MQTT explorer offer the same tree-style browsing outside the portal. If you are testing from a browser only, our guide to using an MQTT web client online pairs well with this one, and MQTT broker monitoring covers the metrics a topic browser does not show.

Explore Your Own Topics on ConnectMQ

Create a free broker at app.connectmq.com on the Serverless plan, with no credit card required. The portal includes the live topic explorer, the in-browser client and the ISA-95 devices view used in this guide. Learn more about ConnectMQ as a fully managed MQTT service, or read the broader guide to choosing a cloud MQTT broker.

Frequently Asked Questions

Can I list all topics on an MQTT broker?

Not directly; MQTT has no topic registry. A topic browser subscribes to a wildcard and shows the topics that receive messages, plus any retained topics, which is the practical equivalent.

Why does my topic browser not show a topic I know exists?

Either nothing has been published to it since you subscribed and it holds no retained message, or your wildcard does not cover it. Check case, leading or trailing slashes, and the subscription root.

Is subscribing to # safe on a production broker?

It works but receives every message, which can be heavy. Subscribe to the narrowest root that answers your question and use a dedicated debugging credential.

How do I delete a topic from the tree?

Topics disappear when nothing holds them. Clear a retained message by publishing an empty retained payload to that exact topic, and stop the publisher that creates it.

Related articles

BYOC MQTT Broker: Bring Your Own Cloud Explained

A BYOC MQTT broker runs in your cloud account but is operated by a provider. Learn how the model works, the responsibility split, and how to prepare your account for it.

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.