Custom strategies
Generate a whole dashboard, a view, or a section from code, and offer it in the dashboard editor.
A strategy is code that writes a dashboard configuration. Instead of a list of cards, the dashboard says "use this strategy", and the strategy generates the cards from what is in the home, every time the dashboard loads. The Overview dashboard Home Assistant builds for you, the areas dashboard, the map, and the energy dashboard are all strategies.
There are three kinds:
- A dashboard strategy generates the whole dashboard, with all its views.
- A view strategy generates one view.
- A section strategy generates one section in a sections view.
They work together. A dashboard strategy often generates a list of views that each use a view strategy, so each view is only built when someone opens it.
A strategy is a custom element with a static generate() method. It gets the strategy configuration and hass, and returns the configuration of what it generates: a dashboard, a view, or a section.
The tag name decides the kind: ll-strategy-dashboard-<name>, ll-strategy-view-<name>, or ll-strategy-section-<name>. The <name> is what follows custom: in the configuration. Everything in the strategy section, including your own options, is passed to generate() as config.
Load your strategy as a resource, like a custom card.
The built-in strategies are good examples. The map dashboard strategy is a small one.
A dashboard strategy returns a whole dashboard configuration:
class MyDemoDashboardStrategy extends HTMLElement {
static async generate(config, hass) {
const name = hass.config.location_name || "Home Assistant";
return {
title: config.title || "My demo dashboard",
views: [
{
title: "Home",
path: "home",
cards: [
{ type: "markdown", content: `# Welcome to ${name}` },
{ type: "entities", entities: ["sun.sun"] },
],
},
],
};
}
}
customElements.define(
"ll-strategy-dashboard-my-demo",
MyDemoDashboardStrategy
);Use it as the configuration of a dashboard:
strategy:
type: custom:my-demo
title: Generated dashboardRegister it with strategyType: "dashboard", and users can pick it under Community dashboards in the dialog to add a dashboard.
A dashboard strategy can suggest the title and icon of the new dashboard with a static getCreateSuggestions(hass). Users can still change both before they create it.
static getCreateSuggestions(hass) {
return { title: "My demo dashboard", icon: "mdi:view-dashboard" };
}A view strategy returns the configuration of one view. Use it in a view of any dashboard:
views:
- strategy:
type: custom:my-area
area: living_roomThe view strategy below shows the entities of one area. The dashboard strategy above it creates a view with that view strategy for every area. hass.areas, hass.devices, and hass.entities hold the registries, so neither has to fetch anything.
class MyAreaDashboardStrategy extends HTMLElement {
static async generate(config, hass) {
return {
title: config.title || "Areas",
views: Object.values(hass.areas).map((area) => ({
title: area.name,
path: area.area_id,
strategy: { type: "custom:my-area", area: area.area_id },
})),
};
}
}
class MyAreaViewStrategy extends HTMLElement {
static async generate(config, hass) {
const areaId = config.area;
const areaDevices = new Set(
Object.values(hass.devices)
.filter((device) => device.area_id === areaId)
.map((device) => device.id)
);
const cards = Object.values(hass.entities)
.filter(
(entity) =>
entity.area_id === areaId ||
(!entity.area_id && areaDevices.has(entity.device_id))
)
.map((entity) => ({ type: "tile", entity: entity.entity_id }));
return { cards: [{ type: "grid", cards }] };
}
}
customElements.define(
"ll-strategy-dashboard-my-area",
MyAreaDashboardStrategy
);
customElements.define("ll-strategy-view-my-area", MyAreaViewStrategy);Both use the name my-area: one as a dashboard strategy, one as a view strategy. That is fine, because the kind is part of the tag name.
A section strategy returns the configuration of one section, with its cards. Use it in a sections view, next to regular sections:
views:
- type: sections
sections:
- strategy:
type: custom:my-domain
domain: lightThe section strategy below shows a tile for every registered entity of one domain, like all the lights:
class MyDomainSectionStrategy extends HTMLElement {
static async generate(config, hass) {
const cards = Object.keys(hass.entities)
.filter((entityId) => entityId.startsWith(`${config.domain}.`))
.map((entityId) => ({ type: "tile", entity: entityId }));
return { type: "grid", cards };
}
}
customElements.define(
"ll-strategy-section-my-domain",
MyDomainSectionStrategy
);Register it with strategyType: "section", and users can pick it under Community sections when they create a section and choose Automatic section. After they pick it, the section editor opens with your settings.
The dialog shows each section strategy as a tile with a live preview. To get one, add a static getStubConfig(hass) that returns a strategy configuration with good defaults. It can be async. The dialog passes it to generate() to render the preview, and the new section starts with it.
static getStubConfig(hass) {
return { type: "custom:my-domain", domain: "light" };
}Include custom: in the type, like in the configuration. Without getStubConfig(), or when it throws, the tile shows no preview. Pick defaults that show something in most homes.
Register your strategy in window.customStrategies, and users can pick it in the dashboard editor without writing any YAML:
window.customStrategies = window.customStrategies || [];
window.customStrategies.push({
type: "my-demo",
strategyType: "dashboard",
name: "My demo dashboard",
description: "A starter dashboard generated from JavaScript.",
documentationURL: "https://example.com/my-demo-dashboard",
});| Key | Required | Meaning |
|---|---|---|
type |
Yes | The name of the strategy, without custom:. |
strategyType |
Yes | "dashboard" or "section". |
name |
The name in the dialog. | |
description |
A short description below the name. | |
documentationURL |
A link to your documentation. The frontend does not show it anywhere yet. |
View strategies can't be picked in the editor yet, so there is no need to register them.
The resource has to be loaded for the dialog to see it. After adding or updating it, reload the page and open the dialog again.
A strategy runs when the dashboard loads. By default, it runs again when the entity, device, area, or floor registry changes, so a new device shows up without a reload. State changes don't trigger it.
To change that, add a static registryDependencies with the registries your strategy depends on: any of entities, devices, areas, floors, labels, and panels. Use an empty list for a strategy that never needs to run again. For full control, add a static shouldRegenerate(config, oldHass, newHass) that returns true when the strategy should run again.
class MyDemoDashboardStrategy extends HTMLElement {
static registryDependencies = ["areas"];
static async generate(config, hass) {
// ...
}
}A strategy can have a visual editor for its options. Return an editor element from the static getConfigElement(). It works like a card editor: Home Assistant calls setConfig() with the strategy configuration and sets hass, and the editor fires config-changed with the new configuration.
Give every section strategy an editor: it is the first thing users see after they pick your section in the dialog. Without one, they only get a YAML editor.
class MyDomainSectionStrategy extends HTMLElement {
static getConfigElement() {
return document.createElement("my-domain-strategy-editor");
}
static async generate(config, hass) {
// ...
}
}A dashboard strategy has two more static options:
configRequired = true: the strategy can't work without settings. Home Assistant shows the editor before it creates the dashboard.noEditor = true: the strategy has no settings to edit.
- Keep
generate()fast. It runs on every load, and the dashboard waits for it. hassalready has the areas, devices, and entities, in a short form. For data it doesn't have, like the full entity registry entries, usehass.callWS(). Do that once, in the dashboard strategy, and pass the result to your view strategies through their configuration.- A strategy can use any card, including your own custom cards.
- Write tests for strategies. They are pure functions from data to configuration, which makes them easy to test.