A Home Assistant MQTT automation reacts to messages on a broker topic and can publish messages back, which lets you automate devices and scripts that never appear as entities. In this tutorial you will connect Home Assistant to a broker, write automations with MQTT triggers, filter on JSON payloads and wildcard topics, publish commands with mqtt.publish, and avoid the loops and race conditions that catch most people out.
Entities or Raw Topics?
Home Assistant offers two ways to use MQTT. The first is entities: MQTT sensors, switches and lights defined in YAML or created through discovery. Automations then use ordinary state triggers. The second is the MQTT trigger, which fires directly on a message without any entity in between.
| Use an entity and a state trigger when | Use an MQTT trigger when |
|---|---|
| You want history, graphs and a dashboard tile | The message is an event, not a state (button press, alarm, RFID scan) |
| The value is long-lived (temperature, switch state) | Payloads vary in shape or carry several fields |
| Other automations reuse the same value | One automation should handle many devices via a wildcard |
Both approaches work together. A typical setup uses entities for states and MQTT triggers for events. If you have not yet defined entities, start with Home Assistant MQTT and the MQTT discovery guide.
Connecting Home Assistant to the Broker
Open Settings > Devices & services > Add integration > MQTT and enter the broker. For a test run you can use the public MQTT test broker at broker.connectmq.com, which accepts anonymous connections:
| Field | Test value | Production value |
|---|---|---|
| Broker | broker.connectmq.com |
broker.connectmq.com |
| Port | 1883 |
8883 |
| Username / Password | empty | YOUR_USERNAME / YOUR_PASSWORD |
| Client ID (advanced) | ha-<random> |
YOUR_CLIENT_ID |
| Broker certificate validation (advanced) | off | Auto |
Advanced options only appear if Advanced mode is enabled in your user profile. The public test area is shared and readable by anyone, so use a unique prefix such as connectmq/demo/k9p4z7/ and never send anything sensitive there. The full production walkthrough is in connecting Home Assistant to an external MQTT broker.
Your First Home Assistant MQTT Automation
The automation below turns on a light when a wireless button publishes pressed. It uses the current automation syntax (triggers:, trigger: mqtt, actions:).
alias: Greenhouse button turns on grow light
triggers:
- trigger: mqtt
topic: connectmq/demo/k9p4z7/greenhouse/button
payload: pressed
actions:
- action: light.turn_on
target:
entity_id: light.grow_light
mode: single
Test it from a terminal:
mosquitto_pub -h broker.connectmq.com -p 1883 -i btn-k9p4z7 \
-t 'connectmq/demo/k9p4z7/greenhouse/button' -m 'pressed'
The payload filter compares the raw string exactly. If the device sends JSON, use value_template to extract a field first; the payload value is then matched against the template result.
triggers:
- trigger: mqtt
topic: connectmq/demo/k9p4z7/greenhouse/button
value_template: "{{ value_json.action }}"
payload: double
Using the Trigger Data in Templates
Inside the automation, the trigger variable carries everything about the message: trigger.topic, trigger.payload, trigger.payload_json (if the payload parses as JSON), trigger.qos and trigger.retain. This turns a single automation into a flexible handler.
alias: Notify on soil moisture alert
triggers:
- trigger: mqtt
topic: connectmq/demo/k9p4z7/garden/+/alert
conditions:
- condition: template
value_template: "{{ trigger.payload_json.moisture | float(100) < 25 }}"
actions:
- action: notify.mobile_app_phone
data:
title: "Dry soil"
message: >
Bed {{ trigger.topic.split('/')[4] }} is at
{{ trigger.payload_json.moisture }}% moisture.
mode: queued
max: 10
The + wildcard matches one level, so one automation covers every bed. trigger.topic.split('/')[4] extracts the bed name from connectmq/demo/k9p4z7/garden/<bed>/alert. Using float(100) gives a safe default if the field is missing, so a malformed message does not raise an error. mode: queued makes sure alerts from several beds arriving together are all processed. For soil sensors that publish this kind of message, see the soil moisture sensor MQTT tutorial.
Publishing MQTT from Automations
The mqtt.publish action sends any payload to any topic. Here a time-based automation sends a watering command to a valve controller and a state change publishes a status summary:
alias: Morning watering command
triggers:
- trigger: time
at: "06:30:00"
conditions:
- condition: numeric_state
entity_id: sensor.rain_last_24h
below: 2
actions:
- action: mqtt.publish
data:
topic: connectmq/demo/k9p4z7/garden/valve/zone1/set
payload: '{"state":"open","duration_s":600}'
qos: 1
retain: false
mode: single
alias: Publish alarm state for other systems
triggers:
- trigger: state
entity_id: alarm_control_panel.home
actions:
- action: mqtt.publish
data:
topic: connectmq/demo/k9p4z7/home/alarm/state
payload: "{{ trigger.to_state.state }}"
retain: true
Two rules of thumb. Commands should normally not be retained, otherwise a device that reconnects tomorrow will replay yesterday's command. States that other systems read on startup should be retained so a new subscriber gets the current value immediately. QoS 1 is worth the extra round trip for commands like this, because a lost message means a bed that stays dry.
Request and Confirm Pattern
For anything that moves water, heat or doors, do not assume a published command succeeded. Wait for the device to confirm:
alias: Open zone 1 and verify
triggers:
- trigger: state
entity_id: input_boolean.water_zone1
to: "on"
actions:
- action: mqtt.publish
data:
topic: connectmq/demo/k9p4z7/garden/valve/zone1/set
payload: open
- wait_for_trigger:
- trigger: mqtt
topic: connectmq/demo/k9p4z7/garden/valve/zone1/state
payload: open
timeout: "00:00:15"
continue_on_timeout: true
- if:
- condition: template
value_template: "{{ wait.trigger is none }}"
then:
- action: notify.mobile_app_phone
data:
message: "Valve zone 1 did not confirm within 15 seconds."
mode: restart
wait.trigger is none when the timeout expired, which is how the automation knows the valve never answered.
Common Pitfalls
- Automation loops. An automation that publishes to a topic it also triggers on will fire forever. Keep command topics (
/set) and state topics (/state) separate. - Retained messages firing triggers. When Home Assistant restarts and resubscribes, retained messages are delivered again and MQTT triggers fire. Add a condition such as
{{ not trigger.retain }}if that is unwanted. - Case and whitespace.
payload: ONdoes not matchonorON\n. Normalise withvalue_template: "{{ value | trim | lower }}". - Wrong wildcard.
#is only valid as the last level;+matches exactly one level. - Silent template errors. Check Settings > System > Logs and the automation trace; traces show the trigger payload, which is the fastest way to debug.
Debugging with a Live Topic View
Before blaming the automation, confirm the message really arrives. Settings > Devices & services > MQTT > Configure has a listen tool that subscribes to any topic. Outside Home Assistant, a desktop MQTT explorer or the broker's web client shows the same traffic. Watching the broker side is useful because it tells you whether a problem is in the device, the broker connection or the automation.
Combining Automations with Broker-Side Alerts
Home Assistant automations only run while Home Assistant is up. If the Raspberry Pi hosting it loses power, nobody is told that the greenhouse is overheating. A useful safety net is a second, independent alert path at the broker. In the ConnectMQ portal you can define threshold alert rules on a topic, for example a temperature above 35, and see triggered alerts in the alerts log even when your home server is down. Keep the clever logic in Home Assistant and use the broker-side rules only for a few critical limits.
The same reasoning applies to device logic. A valve controller should close itself after a maximum run time even if the close command never arrives, because an automation that publishes open and then crashes must not flood the garden.
Moving to Production
Automations that control water or heat should not depend on a public test area where anyone can publish to your topics. Create a private broker, switch the MQTT integration to port 8883 with credentials, and change topic prefixes from connectmq/demo/... to your own, such as home/ or site1/. A managed MQTT broker also lets remote sensors, a holiday house or a second Home Assistant instance publish to the same topics without opening ports on your router. For the wider architecture of devices, broker and logic, read the IoT automation guide.
Automate Home Assistant with ConnectMQ
ConnectMQ provides a private MQTT broker with TLS, generated credentials, a live topic explorer and an in-browser MQTT client, so you can fire test messages at your automations without leaving the browser. Create a free Serverless broker at app.connectmq.com, no credit card required, and point the Home Assistant MQTT integration at it.
Frequently Asked Questions
Can a Home Assistant MQTT trigger use wildcards?
Yes. Both + and # work in the trigger topic, and trigger.topic tells you which concrete topic matched.
How do I read a JSON field from an MQTT trigger?
Use trigger.payload_json.field in templates, or value_template: "{{ value_json.field }}" in the trigger itself to filter on that field.
Why does my automation fire when Home Assistant restarts?
Retained messages are redelivered on resubscribe. Check trigger.retain in a condition, or stop publishing that topic with the retain flag if it is really an event.
Should mqtt.publish commands be retained?
Usually not. Retained commands are replayed to devices when they reconnect, which can open a valve or switch a relay long after the fact.



