A Zigbee2MQTT bridge that’s clearly alive, with devices pairing fine and states updating in the frontend, is one of the more confusing failure states in a Home Assistant setup, because the obvious suspect is innocent. If devices show up and update in the Zigbee2MQTT frontend or in raw MQTT messages but never appear as entities in Home Assistant, or entities that used to work vanish after a restart or an upgrade, the problem is almost always in the discovery handshake between Zigbee2MQTT and Home Assistant’s MQTT integration. It’s rarely the Zigbee network itself.
The official Home Assistant integration page for Zigbee2MQTT documents how to turn discovery on and how to override its payloads. It doesn’t explain what discovery is actually doing under the hood, or why it can go quiet with nothing showing up in any log. I went through the current Zigbee2MQTT settings reference plus one GitHub issue and one community thread to work out the actual mechanism: what triggers discovery, what silently breaks it, and where to look first when it stops working.
This is about Home Assistant never learning an entity exists in the first place, the config-registration handshake. If entities show up fine after setup but flip to “unavailable” specifically after a Home Assistant restart, that’s a different mechanism (bridge/state), not covered here.
If that unavailable-after-restart symptom sounds like your situation instead, our bridge/state article covers it separately. The two get conflated constantly, and they have different root causes.
Devices missing from Home Assistant isn’t always a Zigbee problem
Before touching discovery settings, confirm the Zigbee layer is actually fine. Open the Zigbee2MQTT frontend and check that the device shows current state, or subscribe to its state topic directly with an MQTT client and watch values update when you trigger the device. Our guide to Zigbee2MQTT’s MQTT topics covers how those topics and payloads are structured if you haven’t worked with raw MQTT before.
If state is updating there, the pairing, the mesh, and the MQTT publish path are all working. The device exists, Zigbee2MQTT knows about it, and the broker is passing messages. What’s missing is a second, separate publish: the discovery config message that tells Home Assistant’s MQTT integration the entity exists at all. Home Assistant doesn’t scan for devices on the MQTT bus. It only creates an entity when it receives a specific discovery message describing one. No discovery message, no entity, regardless of how healthy everything else is.
What MQTT discovery actually does
Zigbee2MQTT publishes a retained discovery config message for each exposed entity under a dedicated topic prefix, discovery_topic, which defaults to homeassistant. Home Assistant’s MQTT integration subscribes to that prefix and builds an entity from whatever config message it finds there, using the device class, unique ID, and state topic the message specifies.
The docs are explicit that discovery_topic should be different from your MQTT base topic, the one Zigbee2MQTT publishes actual device state under. Using the same value for both risks the two message streams colliding on the broker.
Because those config messages are published retained, they normally sit on the broker and a reconnecting Home Assistant picks them straight back up. That’s why most restarts recover on their own, and it’s also why the failure, when it does happen, looks so arbitrary.
The birth message: why discovery re-fires, or doesn’t, on restart
Home Assistant’s own MQTT integration publishes a “birth” message (online) when it connects to the broker, and a “will” message (offline) if it disconnects unexpectedly, both on status_topic (default homeassistant/status). Zigbee2MQTT listens for that birth message and uses it as the trigger to republish its discovery configs — the safety net for when the retained copies are gone, typically because the broker restarted without persistence configured.
That handshake depends on both sides naming the same topic, and this is where carried-over configurations break. homeassistant.status_topic has only defaulted to homeassistant/status since Zigbee2MQTT 2.0.0. Before that the default was hass/status. A config file that still pins the old value subscribes to a topic Home Assistant no longer publishes to, so the birth message never arrives, the republish never fires, and nothing on either side logs an error about it.
That is the actual root cause behind GitHub issue #27458, whose title describes discovery messages not being published after an online birth message reached homeassistant/status — device state continuing to publish normally the whole time, which is exactly why it reads as “Zigbee2MQTT is broken” from the outside. It isn’t an open mystery, and it’s the same mismatch we documented in the bridge/state article. If your device state is updating but discovery never re-fires, check the topic on both sides before restarting anything.
enabled defaults to false, and what the 2.x upgrade actually changed
The enabled field in Zigbee2MQTT’s homeassistant config block defaults to false, and it is a restart-required setting. A block that sets discovery_topic or status_topic but never sets enabled has discovery switched off entirely. Nothing errors. Entities just never appear.
This is the cheapest thing to rule out, so check it first. The Zigbee2MQTT settings reference documents the field as a boolean defaulting to false, and a community thread on this exact symptom — opened in 2026 and still unresolved — reaches the same conclusion from a different user’s config and lands on the same fix.
homeassistant: enabled: true status_topic: homeassistant/status
Worth being precise about what the 2.x upgrade did and didn’t do here, because this gets repeated wrongly. Zigbee2MQTT 2.0.0 restructured a lot of settings, but it ships an automatic settings migration: deprecated keys are moved to their new locations on first start, the original configuration.yaml is backed up, and everything the migration changed is written to data/migration-1-to-2.log. So an upgrade doesn’t leave your config looking untouched while quietly disabling things — if you want to know what moved, that log file is the answer. What the upgrade did change, and what the migration can’t guess for you, is the status_topic default described above.
The homeassistant block takes a small set of keys, and discovery isn’t one of them: enabled, discovery_topic, status_topic, and the options controlling entity shape such as legacy_action_sensor and experimental_event_entities, which we cover in the event entities explainer.
Making enabled restart-required is the part I’d push back on. It’s defensible — plenty of settings can’t be applied live — but it means an edited config reads as correct and behaves as though it were never saved, which is a bad combination for a setting whose only symptom is silence.
discovery_topic vs status_topic: what each one controls
These two settings get confused constantly because they’re both MQTT topics living under the same homeassistant prefix by default, but they control completely different things.
| Setting | Default value | What it actually does |
|---|---|---|
discovery_topic |
homeassistant |
The MQTT prefix Zigbee2MQTT publishes discovery config messages under. Docs warn it should differ from your MQTT base topic to avoid the two streams colliding. |
status_topic |
homeassistant/status (was hass/status before 2.0.0) |
Where Home Assistant’s own MQTT integration publishes its birth (“online”) and will (“offline”) messages. Zigbee2MQTT watches this topic to know when to republish discovery configs. |
discovery_topic is the destination for what gets published. status_topic is the trigger for when it gets published. A misconfiguration in either one produces the same symptom, entities missing from Home Assistant, for two entirely different reasons.
A troubleshooting order of operations
Work through these in order rather than restarting services at random:
- Confirm the device itself is fine. Check state updates in the Zigbee2MQTT frontend or raw MQTT state topic. If this is broken, the problem is upstream of discovery entirely.
- Check the
homeassistantblock in your Zigbee2MQTT config for theenabledfield, not just the topics. If it’s absent, discovery is off. - Confirm
discovery_topicdoesn’t collide with your MQTT base topic. If you’ve customized either value, verify they’re still distinct. - Compare
status_topicagainst what Home Assistant is actually publishing to. If your config predates a 2.x upgrade and still sayshass/status, that’s the mismatch — Home Assistant’s default ishomeassistant/status. - Only after the above checks, restart Zigbee2MQTT.
enabledis restart-required, so a corrected config does nothing until you do. Restarting Home Assistant is the separate lever: that’s what makes it re-send its birth message.
FAQ
Why are my Zigbee2MQTT devices not showing up in Home Assistant even though they appear in the Z2M frontend?
The Zigbee and MQTT state pipeline is working, but Home Assistant hasn’t received a discovery config message for those entities. That message is separate from device state and depends on both your homeassistant.enabled setting and the two sides agreeing on status_topic.
What does enabled: true do in Zigbee2MQTT’s homeassistant config block, and do I need it?
It switches Home Assistant MQTT discovery on. It defaults to false, so if your homeassistant block sets topics but never sets enabled, you need to add it. Note there is no discovery key in that block, despite it appearing in a lot of copied snippets.
What is an MQTT birth message and why does it matter for Zigbee2MQTT discovery?
It’s the message Home Assistant’s MQTT integration publishes to status_topic when it connects to the broker, announcing itself as online. Zigbee2MQTT uses it as the signal to republish its discovery configs, so if the two sides are pointed at different topics, that republish never happens.
Why did my Zigbee2MQTT entities disappear after upgrading to 2.x?
Most likely the status_topic default change: 2.0.0 moved it from hass/status to homeassistant/status, and a config pinning the old value stops hearing the birth message. Check data/migration-1-to-2.log to see what the upgrade’s automatic migration did change.
Do I need to restart Zigbee2MQTT, Home Assistant, or the MQTT broker to fix broken discovery?
Zigbee2MQTT, after fixing the config, since enabled and the topic settings are only read at startup. Restarting Home Assistant is useful for a different reason: it re-sends the birth message. Restarting either without addressing the underlying config usually changes nothing.
What this verified, and what to check yourself
This is a documentation and bug-tracker synthesis, not a bench test. I didn’t run a Zigbee2MQTT instance through this failure and reproduce it end to end. What’s confirmed directly from Zigbee2MQTT’s settings reference: the enabled field, its false default and its restart requirement, the defaults for discovery_topic and status_topic, and the absence of any discovery key in that block. The 2.0.0 breaking-changes discussion is the source for the automatic settings migration and its log file. The community thread is unresolved and issue #27458 was closed as not planned, so treat both as reports of the symptom rather than maintainer sign-off on a fix.
All of this stays entirely local. Discovery is a mechanism between your own Zigbee2MQTT instance, your own MQTT broker, and your own Home Assistant install, none of it touches a vendor cloud.
Check your own homeassistant config block for the enabled field first if you’re on this problem right now — it’s the cheapest thing to rule out. If that’s already set correctly and devices are still missing, compare status_topic on both sides before you touch anything else. That single line is where most carried-over configurations break, and it produces exactly the symptom where everything except discovery keeps working.