Create or Update Dashboard
ha_config_set_dashboardCreate or update a Home Assistant dashboard using full config replacement or Python transformations for targeted edits. Supports new, existing, and strategy-based dashboards.
Instructions
Create or update a Home Assistant dashboard.
MUST call ha_get_skill_guide OR refer to your locally installed skills first.
Creates a new dashboard or updates an existing one with the provided configuration. Supports two modes: full config replacement OR Python transformation.
Use 'default' or 'lovelace' to target the built-in default dashboard. New dashboards require a hyphenated url_path (e.g., 'my-dashboard').
WHEN TO USE WHICH MODE:
python_transform: RECOMMENDED for edits. Surgical/pattern-based updates, works on all platforms.
config: New dashboards only, or full restructure. Replaces everything.
IMPORTANT: After delete/add operations, indices shift! Subsequent python_transform calls must use fresh config_hash from ha_config_get_dashboard() to get updated structure. Chain multiple ops in ONE expression when possible.
TIP: Use ha_config_get_dashboard(entity_id=...) to get the path for any card.
PYTHON TRANSFORM EXAMPLES (RECOMMENDED):
Update card icon: 'config["views"][0]["cards"][0]["icon"] = "mdi:thermometer"'
Add card: 'config["views"][0]["cards"].append({"type": "button", "entity": "light.bedroom"})'
Delete card: 'del config["views"][0]["cards"][2]'
Pattern-based update: 'for card in config["views"][0]["cards"]: if "light" in card.get("entity", ""): card["icon"] = "mdi:lightbulb"'
Multi-operation: 'config["views"][0]["cards"][0]["icon"] = "mdi:a"; config["views"][0]["cards"][1]["icon"] = "mdi:b"'
MODERN DASHBOARD BEST PRACTICES:
Use "sections" view type (default) with grid-based layouts
Use "tile" cards as primary card type (replaces legacy entity/light/climate cards)
Use "grid" cards for multi-column layouts within sections
Create multiple views with navigation paths (avoid single-view endless scrolling)
Use "area" cards with navigation for hierarchical organization
DISCOVERING ENTITY IDs FOR DASHBOARDS: Do NOT guess entity IDs - use these tools to find exact entity IDs:
ha_get_overview(include_entity_id=True) - Get all entities organized by domain/area
ha_search(query, domain_filter, area_filter, search_types) - Find entities and config-body references in one call
If unsure about entity IDs, ALWAYS use one of these tools first.
DASHBOARD DOCUMENTATION:
dashboard-guide.md and dashboard-cards.md ship in this response under
skill_contentby default — layout patterns, card-type taxonomy, and worked examples.ha_get_skill_guide — deeper card-type and configuration guidance.
EXAMPLES:
Create empty dashboard: ha_config_set_dashboard( url_path="mobile-dashboard", title="Mobile View", icon="mdi:cellphone" )
Create dashboard with modern sections view: ha_config_set_dashboard( url_path="home-dashboard", title="Home Overview", config={ "views": [{ "title": "Home", "type": "sections", "sections": [{ "title": "Climate", "cards": [{ "type": "tile", "entity": "climate.living_room", "features": [{"type": "target-temperature"}] }] }] }] } )
Create strategy-based dashboard (auto-generated): ha_config_set_dashboard( url_path="my-home", title="My Home", config={ "strategy": { "type": "home", "favorite_entities": ["light.bedroom"] } } )
Note: Strategy dashboards cannot be converted to custom dashboards via this tool. Use the "Take Control" feature in the Home Assistant interface to convert them.
Update existing dashboard config: ha_config_set_dashboard( url_path="existing-dashboard", config={ "views": [{ "title": "Updated View", "type": "sections", "sections": [{ "cards": [{"type": "markdown", "content": "Updated!"}] }] }] } )
Note: When updating an existing dashboard, title/icon/require_admin/show_in_sidebar are also updated if explicitly provided alongside (or instead of) a config change.
STORAGE-MODE vs YAML-MODE DASHBOARDS: This tool only manages storage-mode dashboards (created via UI/API and stored in Home Assistant's storage backend). It does NOT touch YAML-defined dashboards. Two distinct YAML cases exist and this tool covers neither:
"YAML-mode" dashboards: written in their own .yaml file referenced from configuration.yaml under
lovelace: dashboards:. The dashboard itself lives in a separate YAML file but its registration is in configuration.yaml.Dashboards inlined directly in
configuration.yamlunder thelovelace:key (legacy single-dashboard mode). For either YAML case, edit the dashboard's .yaml file directly.ha_config_set_yamlcan update thelovelace:registration entry in configuration.yaml but does NOT touch the dashboard body in the referenced .yaml file.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| icon | No | MDI icon name (e.g., 'mdi:home', 'mdi:cellphone'). Defaults to 'mdi:view-dashboard' | |
| title | No | Dashboard display name shown in sidebar | |
| config | No | Dashboard configuration with views and cards. Omit or set to None to create dashboard without initial config. Mutually exclusive with python_transform. | |
| url_path | Yes | Dashboard URL path (e.g., 'my-dashboard'). Use 'default' or 'lovelace' for the default dashboard. New dashboards must use a hyphenated path. | |
| view_path | No | With return_screenshot: stable Lovelace views[].path to render. | |
| config_hash | No | Config hash from ha_config_get_dashboard for optimistic locking. REQUIRED for python_transform (validates dashboard unchanged). Optional for config (validates before full replacement if provided). | |
| MandatoryBPS | No | ||
| require_admin | No | Restrict dashboard to admin users only. For existing dashboards, only updated when explicitly provided. | |
| BestPracticeKey | No | Read-receipt for the home-assistant-best-practices skill; required when strict best-practices mode is enabled. Not a secret or credential: the current value is an attestation phrase published openly at the top of the skill content served by ha_get_skill_guide. Read that content, then pass the value back verbatim — this round-trip is the server's designed protocol confirming the practices were read before writing. | |
| show_in_sidebar | No | Show dashboard in sidebar navigation. For existing dashboards, only updated when explicitly provided. | |
| python_transform | No | Python expression to transform existing dashboard config. Mutually exclusive with config. Requires config_hash for validation. See PYTHON TRANSFORM SECURITY below for allowed operations. Examples: Simple: python_transform="config['views'][0]['cards'][0]['icon'] = 'mdi:lamp'" Pattern: python_transform="for card in config['views'][0]['cards']: if 'light' in card.get('entity', ''): card['icon'] = 'mdi:lightbulb'" Multi-op: python_transform="config['views'][0]['cards'][0]['icon'] = 'mdi:lamp'; del config['views'][0]['cards'][2]" PYTHON TRANSFORM SECURITY: ✅ ALLOWED: - Dictionary/list access: config['views'][0]['cards'][1] - Slicing: config['views'][0]['cards'][1:3] - Assignment: config['key'] = 'value' - Deletion: del config['key'] or config.pop('key') - List methods: append, insert, pop, remove, clear, extend - Dict methods: update, get, setdefault, keys, values, items - Loops: for, if/else, pass, break, continue - Comprehensions: [x for x in ...], {k: v for ...}, (x for x in ...) - Ternary: x if condition else y - Iterable unpacking (* in calls/literals): f(*xs), [*xs, y] - Dict unpacking (**) in calls and dict literals: {**d, 'k': v} - Keyword arguments: func(key=value) - Lambdas (e.g. for `key=`): sorted(items, key=lambda x: x['score']) - String methods: startswith, endswith, lower, upper, strip, split, join, replace - Safe builtins: isinstance, len, range, enumerate, zip, sorted, reversed, min, max, sum, abs, any, all, round, str, int, float, bool, list, dict, tuple, set ❌ FORBIDDEN: - Imports: import, from, __import__ - File operations: open, read, write - Dunder access: __class__, __bases__, __subclasses__ - Dangerous builtins: eval, exec, compile, getattr, setattr, delattr, hasattr - Function definitions: def, class - Exception handling: try/except (validate with isinstance/in/.get() instead) - While loops: use bounded for loops or comprehensions instead 🎯 PATTERNS: - Filter cards: cards = [c for c in cards if keep(c)] - Skip in a loop: prefer `continue` over an empty `pass` branch (clearer) - Conditionally include: build a new list and `.append(x)` only the cards you want, instead of iterating the original and using if/pass branches to drop entries - Modify in place when possible (single pass, fewer surprises) over reconstructing the entire list | |
| return_screenshot | No | After writing, also return rendered image(s) of the dashboard so you can see what it looks like in a single call (the dashboard creation/iteration loop). Requires the 'dashboard screenshot' beta feature + engine add-on/sidecar; if unavailable, the write result is returned with a warning. |