Building Custom Dashboard Cards
Custom dashboard cards are standard custom elements. You define a class, register it with customElements.define(), and reference it from a dashboard with a custom: prefixed type:
type: custom:my-card
The frontend strips the custom: prefix and instantiates the element with document.createElement("my-card"). If the element is not defined within 2 seconds, an error card is shown in its place until the element upgrades.
The card contract
A card element must implement the following interface (from the frontend source, src/panels/lovelace/types.ts):
interface LovelaceCard extends HTMLElement {
apex?: ApexOS;
preview?: boolean;
layout?: string;
connectedWhileHidden?: boolean;
getCardSize(): number | Promise<number>;
getGridOptions?(): LovelaceGridOptions;
setConfig(config: LovelaceCardConfig): void;
}
setConfig(config) — required
Called with the card's configuration (the YAML/storage object for this card, including type). Called before the element is attached to the DOM, and again whenever the configuration changes. Throw an error from setConfig to reject invalid configuration; the frontend renders an error card with your message.
apex — the state object property
The frontend assigns the full application state object to the card's apex property whenever state changes. It contains the entity states, connection, user, locale, theme information, and the service-call / WebSocket helpers. Re-render from the apex setter (or, with Lit, declare it as a reactive property):
class MyCard extends HTMLElement {
set apex(apex) {
const stateObj = apex.states[this._config.entity];
// ...render
}
setConfig(config) {
if (!config.entity) {
throw new Error("You need to define an entity");
}
this._config = config;
}
getCardSize() {
return 3;
}
}
customElements.define("my-card", MyCard);
getCardSize() — layout hint
Returns the approximate height of the card in units of 50 px rows (a number or a Promise of one). Used by masonry-style views to balance columns.
getGridOptions() — sections layout hint (optional)
For sections-based views, return grid sizing constraints:
getGridOptions() {
return { columns: 6, rows: 2, min_columns: 3, max_rows: 4 };
}
(getLayoutOptions() is the deprecated predecessor and still recognized.)
Static members — editor integration
These are read from the card class (the constructor), not the instance:
getConfigElement()— return a custom element that edits the card's config. The editor element receives the sameapexproperty, gets the current config through its ownsetConfig(config), and reports changes by firing aconfig-changedevent with{ config }indetail.getConfigForm()— a lighter alternative: return{ schema, assertConfig?, computeLabel?, computeHelper? }and the frontend renders a form-based editor for you.getStubConfig(apex, entities, entitiesFallback)— return the initial config used when a user picks your card in the card picker.
Registering in the card picker
To make your card show up (with name and description) in the dashboard card picker, push an entry onto the global window.customCards array:
window.customCards = window.customCards || [];
window.customCards.push({
type: "my-card",
name: "My Card",
description: "A card that does the thing",
preview: true, // show a live preview in the picker
documentationURL: "https://example.com/my-card",
});
The entry type is:
interface CustomCardEntry {
type: string;
name?: string;
description?: string;
preview?: boolean;
documentationURL?: string;
getEntitySuggestion?: (apex, entityId) => CustomCardSuggestion | CustomCardSuggestion[] | null;
}
Parallel registries exist for other custom dashboard building blocks: window.customBadges (badge elements) and window.customCardFeatures (card features; entries may implement isSupported(apex, context) to control when they are offered).
Loading your card: dashboard resources
Card modules are delivered to the browser as dashboard resources. Each resource has a url and a type of module (recommended), js, or css. Resources are managed in the frontend's settings UI or via the WebSocket API (lovelace/resources, lovelace/resources/create|update|delete). The frontend resolves relative resource URLs against the instance base URL, loads module resources as ES modules, and caches js/css resources so each URL is only loaded once per session.
Events a card can fire
Cards communicate upward with composed, bubbling DOM events:
apex-more-infowithdetail: { entityId }— open the entity's more-info dialog.ll-custom— fired by thefire-dom-eventaction for containing code to handle.ll-rebuild— ask the view to re-create the card (e.g. after editing).
Backwards compatibility
The card API surface is apex (state property), window.apexConnection
(connection promise), and apex-* app-level events. A built-in
compatibility bridge — installed before any dashboard resource executes —
keeps older community cards written against earlier community conventions
working unmodified, so existing cards do not need a rewrite to run on
ApexOS. New cards should target the apex names directly.