npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

unifi2mqtt

v2.0.5

Published

Interface between Ubiquiti UniFi network controllers and MQTT (client presence, devices, WLANs)

Downloads

484

Readme

unifi2mqtt

npm License

Ubiquiti UniFi network controller to MQTT: client presence, devices, WLANs

Connects to a UniFi Network controller — a UniFi OS console (Dream Machine, Dream Router, Cloud Key Gen2, Cloud Gateway) or a self-hosted legacy controller — and bridges it to an MQTT broker, following the mqtt-smarthome architecture. Every wireless and wired client gets a retained presence topic, connect/disconnect events arrive instantly over the controller's event websocket, WLANs and device LEDs can be switched, and everything is announced to Home Assistant via MQTT discovery (one device_tracker per client).

Status: 2.0.0 is a complete rewrite that has not yet been verified against a real controller. It follows the documented UniFi OS and legacy APIs and is tested against fixture responses; see Controller compatibility and please report what works and what does not. 1.x users: see Upgrading from 1.x.

Install

npm install -g unifi2mqtt

Requires Node.js ^20.19, ^22.12 or >= 24. unifi2mqtt 2 is built on mqtt-interfaces-core (mqtt-smarthome spec 2.x) like the author's other adapters.

Create a local user on the controller for unifi2mqtt (UniFi OS: Settings → Admins & Users → Admin, "Restrict to local access only"; a read-only role is enough for status, the set topics need an admin). Cloud/SSO accounts and MFA do not work with the API.

Usage

unifi2mqtt --controller https://192.168.1.1 --username unifi2mqtt --password s3cret -k --mqtt-url mqtt://broker

| option | default | description | | ------------------------------------ | ------------------ | -------------------------------------------------------------------------------------------------------------- | | -c, --controller | | controller url, required: https://<console> (UniFi OS) or https://<host>:8443 (legacy controller) | | --username, --password | | controller credentials, required: a local account without 2fa (not a Ubiquiti sso login) | | --site | default | site id (the part after /site/ in the controller url; not the display name) | | --mode | auto | api flavour: unifi-os (UDM, UDR, UCK G2, Cloud Gateway) or legacy (self-hosted controller); auto detects | | -k, --insecure | off | accept the controller's tls certificate without validation (consoles ship self-signed certificates) | | --events | on | subscribe to the controller's event websocket for instant client events; --no-events for polling only | | --poll-interval | 60 | seconds between polls of clients, devices and wlans (min 5) | | --presence-timeout | 0 | seconds a client may be gone before it is reported absent (0 = immediately) | | --clients | all | only publish these clients: mac addresses, names or hostnames, comma separated | | --client-key | name | how clients are named in topics: name (alias, else hostname, else mac), hostname or mac | | --ha-clients | on | announce a device_tracker per client to Home Assistant; --no-ha-clients for controller and devices only | | --publish-raw | off | additionally publish every websocket frame of the controller as <name>/raw | | -u, --mqtt-url | mqtt://localhost | broker URL, see MQTT.js | | --mqtt-username, --mqtt-password | | broker credentials | | --mqtt-tls-ca | | CA certificate file for mqtts:// | | -n, --name | unifi | instance name, used as topic prefix | | --json-payloads | on | status as {"val", "ts", "lc"} JSON; --no-json-payloads for plain values | | --ha-discovery | on | Home Assistant MQTT discovery (--no-ha-discovery disables and clears it) | | --ha-prefix | homeassistant | discovery prefix | | --maintenance | on | accept <name>/maintenance/set/{loglevel,restart}; --no-maintenance disables | | -v, --verbosity | info | error, warn, info, debug | | --install / --uninstall | | install/remove the systemd service unifi2mqtt@<name> | | --config-schema | | print the JSON Schema of all options and exit |

Every option can also be set via environment variable with the prefix UNIFI2MQTT_, e.g. UNIFI2MQTT_CONTROLLER=https://192.168.1.1 UNIFI2MQTT_PASSWORD=… unifi2mqtt; the broker settings fall back to the unprefixed MQTT_URL, MQTT_USERNAME, MQTT_PASSWORD.

Run as a systemd service

sudo unifi2mqtt --install --name unifi -c https://192.168.1.1 --username unifi2mqtt --password s3cret -k -u mqtt://192.168.1.2

--install creates a system user unifi2mqtt, writes the given options to /etc/unifi2mqtt/<name>.env (UNIFI2MQTT_* variables, 0640 root:unifi2mqtt — edit and systemctl restart unifi2mqtt@<name> to change), installs the template unit /etc/systemd/system/[email protected] and enables + starts unifi2mqtt@<name>. The instance name is the --name option, i.e. the MQTT topic prefix. Broker settings shared by all mqtt-interfaces adapters on the host can go to /etc/mqtt-interfaces/broker.env (MQTT_URL, MQTT_USERNAME, MQTT_PASSWORD). Logs: journalctl -u unifi2mqtt@<name> -f.

Several sites or controllers: run --install once per site with a different --name — each becomes its own instance with its own config and topic prefix, sharing one template unit and one system user:

sudo unifi2mqtt --install --name unifi        -c https://192.168.1.1 --site default …
sudo unifi2mqtt --install --name unifi-office -c https://192.168.1.1 --site office …
systemctl status 'unifi2mqtt@*'

sudo unifi2mqtt --uninstall --name unifi-office removes one instance (the template unit goes with the last one). she can install, configure and update instances from its Services page (config form from --config-schema, the password masked).

Docker

docker run -d --name unifi2mqtt --restart unless-stopped \
  -e UNIFI2MQTT_CONTROLLER=https://192.168.1.1 \
  -e UNIFI2MQTT_USERNAME=unifi2mqtt \
  -e UNIFI2MQTT_PASSWORD=s3cret \
  -e UNIFI2MQTT_INSECURE=true \
  -e UNIFI2MQTT_MQTT_URL=mqtt://broker \
  ghcr.io/hobbyquaker/unifi2mqtt

Topics

<name> defaults to unifi. Names of clients, devices and WLANs are used as topic levels after sanitising: whitespace, /, + and # become _ (Home WiFi → Home_WiFi); duplicate client names get a _<last 4 mac digits> suffix.

<name>/connected

Retained. 0 = not connected to the broker (set via last will), 1 = connected to the broker but not to the controller (login failed, controller unreachable, last poll failed), 2 = the controller answers.

<name>/info and <name>/maintenance/set/…

<name>/info (retained JSON) describes the running instance: package name and version, mqtt-smarthome spec version, node version, host, pid, start time, controller url, site, detected api flavour (mode), whether the event websocket is connected (events), poll interval. <name>/maintenance/set/loglevel (error|warn|info|debug) changes the log level at runtime, <name>/maintenance/set/restart exits cleanly so the service manager restarts the process; --no-maintenance turns both off.

<name>/status/<item>

Retained status reports (except events), published after every poll and instantly on websocket events — only when the value changed. Every status is {"val": <value>, "ts": <ms received>, "lc": <ms last changed>}; with --no-json-payloads the plain value (objects as JSON).

| item | type | notes | | -------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | client/<key>/present | bool | true while the controller lists the client (or a connect event arrived), false after a disconnect or a poll without it (see below) | | client/<key>/details | object | {mac, name, hostname, ip, wired, ssid, band, channel, ap, network} — band is 2.4 GHz / 5 GHz / 6 GHz, ap the access point / switch name, null fields are unknown | | event/client | object | not retained: {type, client, mac, hostname, ssid, ap, wired, guest, time, msg}; type is connected, disconnected, roam, roam_radio | | client_count | int | clients present, all networks | | client_count/wireless | int | wireless clients present | | client_count/wired | int | wired clients present | | wifi/<ssid>/enabled | bool | WLAN enabled — settable | | wifi/<ssid>/client_count | int | clients on this SSID | | device/<key>/online | bool | access point / switch / gateway is connected to the controller | | device/<key>/led | string | on, off or default (site setting) — settable | | device/<key>/clients | int | clients on this device | | device/<key>/details | object | {mac, name, model, type, ip, version} |

<key> of a client is, with the default --client-key name, the alias set in the controller, else the hostname, else the mac address (--client-key hostname / mac to choose). A client that was seen once keeps its topics; when it is renamed the old topics are cleared and the new ones published. With --clients only the listed clients get topics (counts still cover everyone).

After a restart the adapter reads back what it retained on the broker in a previous run and, once the first poll is in, clears every client/…, device/… and wifi/… topic the controller no longer reports — a client that left while the adapter was down, a renamed client, a client now excluded by --clients. Nothing stays present: true from a previous life (#22); the log says how many items were cleared. Needs mqtt-interfaces-core 0.16; on an older core the readback is skipped.

Presence comes from two sources: the client list (stat/sta) polled every --poll-interval seconds, and the controller's event websocket (connect / disconnect / roam events, instant). A client disappears (present: false) when a disconnect event arrives or a poll no longer lists it — immediately with the default --presence-timeout 0, or after that many seconds without a sighting, which smooths phones that drop off the WLAN for a moment. Devices and WLANs are polled with the clients; device events (adopted, restarted, …) trigger an extra refresh.

<name>/set/<item>

Change requests. Payload is a plain value or mqtt-smarthome style JSON ({"val": true}).

| topic | payload | | ----------------------------------- | ----------------------------------------------------------------------------------------------------- | | <name>/set/wifi/<ssid>/enabled | true/false, 1/0, on/off | | <name>/set/device/<key>/led | on, off, default (booleans map to on/off) | | <name>/set/device/<key>/provision | any payload — force-provision the device (cmd/devmgr), for an AP that did not pick up a WLAN change |

mosquitto_pub -t unifi/set/wifi/Guests/enabled -m false
mosquitto_pub -t unifi/set/device/AP_Living_Room/led -m off

The status is re-read from the controller after the command; a rejected command is logged at warn.

<name>/raw

With --publish-raw every frame the controller sends on its event websocket is published as <name>/raw (not retained) — useful to see what your controller version emits (sta:sync, device:sync, alert, events, …) and to collect fixtures.

Home Assistant

MQTT discovery is on by default (HA ≥ 2024.11, device-based discovery). The controller/site appears as a device with client count sensors and one switch (enabled) + one sensor (clients) per WLAN. Every access point, switch and gateway is its own device (linked via the bridge) with a connectivity sensor, an LED select (on/off/default) and a client count. Every client is a device with a device_tracker entity (source_type: router, home/not_home) whose attributes are the client's details (mac, ip, ssid, ap, …); --no-ha-clients skips the clients, --clients limits them. Availability follows <name>/connected.

Device and entity ids are based on mac addresses (unifi2mqtt_<name>_client_<mac>), so renaming a client in the controller keeps its HA history. --no-ha-discovery disables discovery and removes the announcements on startup; --ha-prefix changes the discovery prefix.

Controller compatibility

unifi2mqtt talks to the controller directly (no node-unifi, no ubnt-unifi): POST /api/auth/login + /proxy/network/api/s/<site>/… on UniFi OS, POST /api/login + /api/s/<site>/… on legacy controllers, the session cookie and CSRF token as the ui uses them, the event websocket at …/wss/s/<site>/events. The account must be a local controller user without two-factor authentication — a Ubiquiti SSO login does not work with the local login endpoints, and a 2FA challenge cannot be answered non-interactively; the adapter says so in the log instead of retrying. --mode auto decides by GET /: a UniFi OS console answers 200, a legacy controller redirects to /manage. --mode unifi-os / legacy overrides.

| controller | status | | ------------------------------------------------------ | ----------------------------------------------------------------- | | UniFi OS consoles (UDM, UDM Pro/SE, UDR, UCK G2+, UCG) | implemented after the documented api, not yet verified | | legacy self-hosted Network application (:8443) | implemented after the 1.x adapter's api use, not yet verified |

A login the controller rate-limits (HTTP 429 — UniFi OS answers AUTHENTICATION_FAILED_LIMIT_REACHED after too many failed logins and locks the account for a while) is not retried every 10 s, because each attempt can extend the lockout: the adapter waits for Retry-After when the controller sends it, else 5 minutes, doubling up to 30, and says so at warn on every attempt (#23).

Run with --verbosity debug to see every request and websocket frame. The quickest way to help is a dump from your controller:

git clone https://github.com/hobbyquaker/unifi2mqtt && cd unifi2mqtt && npm ci
node scripts/dump.js -c https://192.168.1.1 --username unifi2mqtt --password … -k --seconds 120

It logs in, fetches stat/sta, stat/device, rest/wlanconf and a few more endpoints, listens to the event websocket for --seconds (connect or disconnect a wifi client meanwhile) and writes everything to ./dump/, redacted: macs keep their vendor part, public ips, names, SSIDs, serials and every secret are replaced consistently. Skim the files, then attach the directory to a GitHub issue together with the controller model and Network version. Nothing is written to the controller, no broker is needed.

Upgrading from 1.x

unifi2mqtt 2.0 is a rewrite on mqtt-interfaces-core; the topic layout changed:

| 1.x | 2.0 | | ----------------------------------------------------------- | ----------------------------------------------------------------------------- | | status/wifi/<ssid>/client/<hostname> {val, mac, ts} | status/client/<key>/present {val, ts, lc} + status/client/<key>/details | | status/wifi/<ssid>/event/connected {val: hostname, …} | status/event/client {val: {type: "connected", client, mac, ssid, ap, …}} | | status/wifi/<ssid>/event/disconnected | status/event/client with type: "disconnected" | | status/wifi/<ssid>/clientCount | status/wifi/<ssid>/client_count | | status/wifi/<ssid>/enabled, set/wifi/<ssid>/enabled | unchanged (SSID sanitised: Home WiFi → Home_WiFi) | | status/clientCount (wireless) | status/client_count (all), status/client_count/wireless, …/wired | | status/device/<name>/led, set/device/<name>/led | unchanged (name sanitised) + device/<name>/online, /clients, /details | | -a/--unifi-host, -p/--unifi-port | -c/--controller https://host[:port] | | -c/--unifi-user, -s/--unifi-password, -w/--unifi-site | --username, --password, --site | | -u/--url | -u/--mqtt-url (--url alias kept) | | env unifi-password=… | UNIFI2MQTT_PASSWORD=… (see --config-schema) | | pm2 | sudo unifi2mqtt --install --name unifi … or Docker |

Wired clients, UniFi OS consoles, <name>/info, maintenance topics, --presence-timeout, --clients, Home Assistant discovery and the systemd installer are new. See CHANGELOG.md.

License

MIT © Sebastian Raff