Some hardware links on this page are affiliate links. As an Amazon Associate I earn from qualifying purchases. We may earn a commission if you buy through them, at no extra cost to you. See the affiliate disclosure.
A single Thread border router covers the room it sits in and then the mesh thins out. An ESP32-C6 already has an 802.15.4 radio. ESPHome’s OpenThread component can join the Thread network you already use for Matter as a full Thread device (device_type: FTD). A full device is router-eligible: the network promotes it to a router when the mesh needs another one. It stays powered, extends coverage for Matter-over-Thread devices, and speaks the ESPHome API over that same network. No Wi-Fi block is required.
The dataset and the API key stay in secrets.yaml. Never paste a Thread TLV, network key, or API key into a published YAML. The TLV is the network key.
What problem it solves
You want another always-on Thread router in a dead spot, built from an ESP32-C6 and the border router Home Assistant already has, without a second commercial hub.
How it works
- Version — OpenThread arrived in ESPHome 2025.6.0 for ESP-IDF builds on the ESP32-C6 and ESP32-H2. 2025.6.1 fixed applying a dataset from Home Assistant’s TLV and from the individual fields. Flash 2025.6.1 or newer. The ESP32-C6 does not build this component with the Arduino framework. Older releases will not join.
- Border router — Home Assistant’s Thread integration, with a border router already on the network (the OpenThread border router add-on, or another border router whose dataset Home Assistant shows as the preferred network). This board does not create a network. It joins yours.
- Dataset — Settings → Devices & services → Thread → the info icon on the preferred network → Active dataset TLVs. That hex string goes in
secrets.yamlasthread_tlv. It carries the channel, name, PAN ids, key, and mesh prefix.force_dataset: truewrites it over anything already stored on the chip, which is what you want the first time and after you rotate the network. - Role —
device_type: FTDis a router-eligible device. ESPHome has no separate “be a router” flag. The stack promotes a powered full device when the partition needs another router. Do not setpoll_period. That setting is for a minimal sleepy device, and a sleepy device does not route. Leave the board on USB power. - API —
network.enable_ipv6is required. The native API and later OTA use the Thread route into your LAN, so IPv6 has to be working between Home Assistant and the border router. Encrypt the API with!secret api_encryption_key. First flash is USB. After Home Assistant adopts the node, updates can go over Thread. - Matter — Thread is the transport under Matter-over-Thread. A router here extends that mesh. The board itself is an ESPHome node, not a Matter bridge.
Hardware notes
- Board. Any ESP32-C6. The linked Super Mini class board is enough: the chip’s 802.15.4 radio is on the module. A Seeed XIAO ESP32-C6 is the board the linked project flashes; it adds an antenna switch on GPIO3, GPIO14, and GPIO15. Do not copy that switch onto a Super Mini. On the C6, GPIO15 is a strap pin, and GPIO8, GPIO9, GPIO12, and GPIO13 are LED, boot, and USB.
- Power. A router is always on. USB power, not a battery. Metal boxes and server racks detune the antenna. Place it where Matter devices already struggle, not inside a closed metal cabinet.
- Network. One preferred Thread network. A phone-commissioned Apple or Google network and a Home Assistant border router are often different datasets. Copy the TLV from the network your Matter devices actually use. The Thread panel’s preferred network is the one to join.
- No Wi-Fi credentials. This YAML has no
wifi:block. Do not add one unless you have a reason; the join path is Thread.
Snippet
substitutions:
device_name: thread-router
friendly_name: Thread router
esphome:
name: ${device_name}
friendly_name: ${friendly_name}
esp32:
variant: esp32c6
board: esp32-c6-devkitc-1
framework:
type: esp-idf
network:
enable_ipv6: true
openthread:
device_type: FTD
tlv: !secret thread_tlv
force_dataset: true
api:
encryption:
key: !secret api_encryption_key
ota:
- platform: esphome
logger:
sensor:
- platform: uptime
name: Uptime
update_interval: 60s
secrets.yaml next to it (replace both placeholders locally; do not commit the file):
thread_tlv: "replace-with-the-active-dataset-tlv"
api_encryption_key: "replace-with-a-generated-key"
Generate the API key locally (openssl rand -base64 32 or the key ESPHome offers when you create a device). Paste the Thread panel’s TLV into thread_tlv. The device YAML reads those entries with !secret. The firmware image then contains the dataset, so do not share the compiled binary either.
force_dataset: true overwrites a dataset already stored on the board. Set it while you are bringing the node onto a known network. After the node has joined and you are no longer changing networks, you can leave it true so a secrets update still wins, or drop it so a later flash does not clobber a dataset you commissioned another way.
Setup outline
- Confirm Home Assistant shows a preferred Thread network and at least one border router. Copy the active dataset TLV from that network’s info dialog into
secrets.yaml. Create an API encryption key the same way. - Install ESPHome 2025.6.1 or newer. Plug the ESP32-C6 in over USB and flash. The log should show OpenThread starting and then a role. Router-eligible is enough at first; promotion to router depends on the mesh, not on a second option in the YAML.
- Adopt the device under Settings → Devices & services. Uptime should update without the board being on Wi-Fi.
- Move it to the weak spot, keep it powered, and watch whether Matter devices that used to drop now have another router in the topology (Thread panel, or the device’s own neighbors if you add diagnostics later).
- Later firmware updates go through the API over Thread. If you change the TLV, flash over USB once so the new dataset is actually written.
Troubleshooting
- Compile error on
openthreador the C6. The ESPHome version is older than 2025.6.1, or the framework is Arduino. Use ESP-IDF. 2025.6.0 introduced the component; dataset setup was fixed in 2025.6.1, so do not stop on 2025.6.0 if the TLV will not apply. - It never joins. The TLV is from a different network than the border router the devices use, or IPv6 is off (
enable_ipv6missing, or the LAN does not route the border router’s Thread prefix). Recopy the preferred network’s dataset. - It joins and then will not route.
device_typeisMTD, orpoll_periodis set. Sleepy devices do not get promoted. Clear the board’s stored OpenThread state if you previously flashed an MTD build (the ESPHome docs note that switching back to FTD does not restore router eligibility until non-volatile storage is cleared) and flash the FTD YAML again. - API adoption fails after a good join. Encryption keys differ, or Home Assistant cannot route to the node. The border router has to be up. Delete a stale device entry and adopt again after you rotate the key.
- XIAO antenna block on a Super Mini. GPIO15 is a strap. Remove any antenna-switch GPIO from a board that does not have that RF switch. With no external antenna fitted, leave the chip antenna selected or the range collapses.
- Shared a compiled binary. That file contains the TLV. Rotate the Thread credentials if a binary left your machine, then flash the new dataset over USB.
Credits
Based on esp32c6-thread-router. OpenThread’s minimum release and the FTD / TLV keys are from the ESPHome 2025.6.0 release notes and the OpenThread component (dataset setup fixes shipped in 2025.6.1). This write-up is an original FTD join for an ESP32-C6. The linked project also publishes diagnostic sensors and a XIAO antenna switch aimed at a newer ESPHome than 2025.6.1; those extras are not required to join as a router-eligible device.