6.6 KiB
HA Lovelace Dashboard Enumeration via WebSocket API
Enumerating and reading HA Lovelace dashboard configurations requires the WebSocket API, not REST. The REST endpoints (/api/config/dashboard/config, /api/lovelace/config) return 404 — they do not exist.
Listing All Dashboards
(async () => {
const ha = document.querySelector("home-assistant");
const conn = ha.hass.connection;
const dashboards = await conn.sendMessagePromise({
type: 'lovelace/dashboards/list'
});
// Returns array of: { id, title, url_path, icon, mode, require_admin, show_in_sidebar }
return JSON.stringify(dashboards, null, 2);
})()
Reading a Dashboard's Full Config (Views + Cards)
(async () => {
const ha = document.querySelector("home-assistant");
const conn = ha.hass.connection;
// Use url_path (the URL slug), NOT the dashboard id
// e.g. 'dashboard-haus', 'stromerzeugung-verbrauch', 'lovelace' (default)
const cfg = await conn.sendMessagePromise({
type: 'lovelace/config',
url_path: 'dashboard-haus' // ← URL slug from dashboards/list
});
// Returns: { title, views: [{ title, path, icon, cards: [...], badges: [...] }] }
// Empty dashboards return { views: [] }
return JSON.stringify(cfg, null, 2);
})()
Key Details
| Parameter | Correct key | Wrong key | Error if wrong |
|---|---|---|---|
| Dashboard selector | url_path |
url |
"extra keys not allowed @ data['url']" |
| Dashboard value | URL slug (e.g. dashboard-haus) |
Dashboard ID (e.g. dashboard_haus) |
Various |
- The default overview dashboard uses
url_path: 'lovelace'. sendMessagePromiseis the async wrapper for WebSocket commands onconn. It returns a Promise that resolves to the response object.- All dashboards in this homelab use
mode: 'storage'(managed via UI, not YAML files). - The REST API endpoints
/api/lovelace/config/{url}and/api/config/dashboard/configboth return 404. Only the WebSocket API works.
Enumerating Card Types Across All Dashboards
Useful for auditing dashboard complexity (e.g. finding overloaded views):
(async () => {
const ha = document.querySelector("home-assistant");
const conn = ha.hass.connection;
const dashboards = await conn.sendMessagePromise({type: 'lovelace/dashboards/list'});
const report = {};
for (const dash of dashboards) {
try {
const cfg = await conn.sendMessagePromise({
type: 'lovelace/config',
url_path: dash.url_path
});
const views = (cfg.views || []).map(v => ({
title: v.title,
path: v.path,
icon: v.icon,
cardCount: (v.cards || []).length,
cardTypes: (v.cards || []).map(c => c.type).filter(Boolean)
}));
report[dash.url_path] = { title: dash.title, views };
} catch(e) {
report[dash.url_path] = { error: String(e).substring(0, 100) };
}
}
return JSON.stringify(report, null, 2);
})()
Writing Dashboard Config Back
To modify a dashboard, send the full config object back via WebSocket:
(async () => {
const ha = document.querySelector("home-assistant");
const conn = ha.hass.connection;
// Read current config
const cfg = await conn.sendMessagePromise({
type: 'lovelace/config',
url_path: 'dashboard-haus'
});
// Modify (e.g. add a view, rearrange cards)
cfg.views.push({
title: 'New View',
path: 'new-view',
cards: [{ type: 'entities', entities: ['sensor.example'], title: 'Example' }]
});
// Save back
await conn.sendMessagePromise({
type: 'lovelace/config/save',
url_path: 'dashboard-haus',
config: cfg
});
return 'Saved';
})()
This Homelab's Dashboard Inventory (as of 2026-07-10, post-redesign)
| Dashboard | URL Path | Views | Cards (main view) | Status |
|---|---|---|---|---|
| Haus (NEW) | dashboard-haus |
5 | 6/3/5/4/5 | ✅ Mushroom + Bubble, mobile-first |
| Stromerzeugung/-verbrauch | stromerzeugung-verbrauch |
5 | 30/1/4/4/1 | ✅ All views filled |
| Übersicht (backup) | lovelace |
3 | 43+1+1 | Old overview, kept as backup |
| IT | dashboard-it |
1 | 16 | OK |
| Karte | map |
1 | 1 | Minimal |
| Editor | dashboard-editor |
1 | 1 | Tool (config-editor-card) |
Deleted (2026-07-10): dashboard-test, Typen, Home_Auto, Moonraker (4 empty ghost dashboards).
Custom cards installed: power-flow-card-plus, price-timeline-card, config-editor-card, platinum-weather-card, xiaomi-vacuum-map-card, apexcharts-card, kiosk-mode, mushroom (v5.1.1), bubble-card (v3.2.4).
Deleting Dashboards
await conn.sendMessagePromise({
type: 'lovelace/dashboards/delete',
dashboard_id: 'dashboard_test' // ← underscore ID from dashboards/list
});
// Returns: null on success
Key: dashboard_id uses underscores (e.g. dashboard_test), while lovelace/config uses url_path with hyphens (e.g. dashboard-test). Always use id from the dashboards/list response for deletion.
Listing Lovelace Resources (Custom Cards)
const resources = await conn.sendMessagePromise({type: 'lovelace/resources'});
// Returns: [{id, url, type}]
// HACS auto-registers installed cards here. Example:
// /hacsfiles/lovelace-mushroom/mushroom.js?hacstag=444350375511
// /hacsfiles/Bubble-Card/bubble-card.js?hacstag=680112919324
Use this to verify HACS installations landed correctly, or to audit which custom cards are available for dashboard configs.
Pitfalls
- REST API 404 for dashboard config. Neither
/api/lovelace/confignor/api/config/dashboard/configexist as REST endpoints. Use WebSocketlovelace/configwithurl_pathparameter. urlvsurl_pathparameter name. Usingurlinstead ofurl_pathcauses"extra keys not allowed @ data['url']"error.- Empty dashboards return
{ views: [] }, not an error. Don't treat missing views as a failure — the dashboard exists but has no content. - Dashboard ID uses underscores, URL path uses hyphens.
dashboard_haus(id) →dashboard-haus(url_path). Always useurl_pathforlovelace/configqueries, butdashboard_id(underscore form) forlovelace/dashboards/delete. sendMessagePromisenotsendMessage. The plainsendMessageis fire-and-forget. UsesendMessagePromiseto get the response.- Verify entity IDs before saving. Query
ha.hass.statesfor each entity ID before writing a dashboard config. Scene names may differ (scene.alles_aus_2notscene.alles_aus), binary sensors may lackdevice_class(filter by name keywords), and some sensors may beunavailable. Dead entity references produce "Entity not available" cards.