Files

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'.
  • sendMessagePromise is the async wrapper for WebSocket commands on conn. 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/config both 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/config nor /api/config/dashboard/config exist as REST endpoints. Use WebSocket lovelace/config with url_path parameter.
  • url vs url_path parameter name. Using url instead of url_path causes "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 use url_path for lovelace/config queries, but dashboard_id (underscore form) for lovelace/dashboards/delete.
  • sendMessagePromise not sendMessage. The plain sendMessage is fire-and-forget. Use sendMessagePromise to get the response.
  • Verify entity IDs before saving. Query ha.hass.states for each entity ID before writing a dashboard config. Scene names may differ (scene.alles_aus_2 not scene.alles_aus), binary sensors may lack device_class (filter by name keywords), and some sensors may be unavailable. Dead entity references produce "Entity not available" cards.