API Reference
Every public export of @glasshome/widget-sdk, with signatures, by entry point. The Widget SDK guide walks the important APIs with worked examples. Anything not listed here is internal and may change without notice.
Entry points
| Import | Use it for |
|---|---|
@glasshome/widget-sdk |
Everything you use at runtime: defineWidget, components, hooks, the data layer, utilities, theming. |
@glasshome/widget-sdk/schemas |
Zod schemas for validating manifests and publish payloads. Used by the CLI and Hub. |
@glasshome/widget-sdk/vite |
Build plugins and helpers for vite.config.ts. The scaffold wires these for you. |
@glasshome/widget-sdk/tailwind-sources |
A CSS file the build feeds to Tailwind. Referenced by the scaffold; not imported in widget code. |
Most types named below are exported too. SolidJS conventions apply: Accessor<T> is () => T, and an argument typed Accessor<X> | X accepts a plain value or an accessor.
Always import the data layer from the SDK
Home Assistant hooks (useEntity, useService, …) are re-exported from the SDK on purpose. Importing them from @glasshome/sync-layer directly fails the build and would bundle a second, disconnected store. See Capabilities.
@glasshome/widget-sdk (main entry)
Core
function defineWidget<C = Record<string, unknown>>(
definition: WidgetDefinition<C>
): WidgetDefinition<C>
Declares a widget. When definition.configSchema is present, it auto-populates manifest.schema (JSON Schema) and manifest.defaultConfig. See the guide.
const SDK_VERSION: string // the installed package version, e.g. "1.2.0"
The host compares SDK_VERSION against each widget’s manifest.sdkVersion range.
Components
Widget carries its slots as static members (Widget.Icon, Widget.Title, …); each is also exported standalone.
| Export | Props (key) | What it is |
|---|---|---|
Widget |
variant, tone, color, colorTo, gradient, loading, emptyState, gestures, class |
The container: shell, gestures, theming. See Widget component. |
WidgetContent / Widget.Content |
children, class? |
Layout wrapper. |
WidgetIcon / Widget.Icon |
icon, color?, dimmed?, entityCount? |
Entity icon tile with adaptive color. |
WidgetTitle / Widget.Title |
children |
Primary label. |
WidgetStatus / Widget.Status |
children |
State text. |
WidgetValue / Widget.Value |
children |
Numeric/formatted value. |
WidgetSliderFill / Widget.SliderFill |
value (0–100), color?, isDragging? |
Animated background fill for slider widgets. |
WidgetDialog |
see WidgetDialog component | Settings/detail dialog. Renders no chrome of its own; inject UI via props. |
Widget hooks
function useWidgetContext(): ReactiveWidgetContext
Reactive widget context. Must be called inside the <Widget> tree.
interface ReactiveWidgetContext {
updateConfig: (config: Record<string, unknown>) => void;
dimensions: () => { width: number; height: number }; // deprecated since 1.9.0, use useWidgetDimensions()
registerDialogOpener?: (open: ((tab?: string) => void) | null) => void;
callService?: ServiceCallFn; // present when capability-routed by the host
}
isEditMode was removed in 1.9.0 (nothing read it; the host provided a constant false).
function useWidgetDimensions(): () => { width: number; height: number }
Measured shell size in CSS px. Call it in a component inside <Widget>; it throws anywhere else.
ctx.dimensions() is deprecated (removed in 2.0.0): outside <Widget> it silently read the host’s stub, which returns (0,0) forever, locking a widget into its smallest layout. This hook throws instead.
function useWidgetEntityGroup<TData = unknown>(
options: UseWidgetEntityGroupOptions<TData>
): UseWidgetEntityGroupResult<TData>
Aggregate one or many entities with presets + empty state. Options: reactive entities: Accessor<EntityView[]>, an aggregationMode ("light" \| "sensor" \| "switch" \| "binary-sensor" \| "none") or a custom calculateGroupData, plus emptyStateConfig, sensorGroupType, allEntitiesMode, and minEntities. Returns entities, groupData, aggregatedData, emptyState, hasEntities, and count accessors. Full tables in the guide.
function useWidgetDialog(defaultTab = "controls"): WidgetDialogReturn
Open/close + active-tab state for WidgetDialog. Returns showDialog, setShowDialog, openDialog, closeDialog, activeTab, setActiveTab, and a dialogProps bag to spread onto <WidgetDialog>.
function useWidgetGestures(
config: () => GestureConfig,
orientation?: () => "horizontal" | "vertical" | "square"
): GestureHandlers
Tap, hold, and slide gestures for any pointer. GestureConfig = { tap?, hold?: { action, delay? }, slide?: { value, onChange, min?, max?, orientation?, activationDelay? } }. Pass the returned GestureHandlers to <Widget gestures={…} />; call .dispose() in onCleanup.
function useReducedMotion(): Accessor<boolean> // prefers-reduced-motion; false if unavailable (SSR-safe)
function useIntersectionPause(el: Accessor<Element | undefined>): Accessor<boolean> // true while off-screen
| Export | Kind | What it is |
|---|---|---|
WidgetCtx |
SolidJS context | The context object behind useWidgetContext. Advanced; prefer the hook. |
Config schema helpers
Since 1.4.0 the config surface is defineConfig + field.* (see the Config API):
function defineConfig<S>(shape: S): ConfigSchema<S>;
const field: {
title(): Field<string | undefined>;
text(o): Field<string>;
number(o): Field<number>;
toggle(o): Field<boolean>;
choice<T>(values: readonly T[], o): Field<T>;
entities(domain: string, o?): Field<string[]>;
entity(domain: string, o?): Field<string[]>;
area(o?): Field<string | undefined>;
stringList(o): Field<string[]>;
group<S>(shape: S, o): Field<Infer<S>>;
};
type Infer<C>; // config type from a schema, without naming zod
z is re-exported from the SDK as a permanent escape hatch for schemas field.* can’t express.
// Deprecated — use field.* / defineConfig (removed in a future major).
const widgetFields: {
title(): ZodType; // → field.title()
entityIds(domain: string, opts?: { deviceClass?: string }): ZodType; // → field.entities(domain, opts)
singleEntity(domain: string, opts?: { deviceClass?: string }): ZodType; // → field.entity(domain, opts)
areaId(): ZodType; // → field.area()
}
widgetFields and raw z.object config are deprecated. Run bun widget migrate config. See the Config API.
Home Assistant data hooks
Reactive accessors over the host’s live store. Each entityId arg accepts a string or an accessor. Declare every domain you read in your capabilities: that declaration is what the user sees and approves when they install your widget.
| Export | Signature | What it is |
|---|---|---|
useEntity |
(id: Accessor<string> | string) => Accessor<EntityView | undefined> |
One entity by id. |
useEntities |
(ids: Accessor<string[]>) => Accessor<EntityView[]> |
Many entities from an id-array accessor. |
useArea |
(id: Accessor<string> | string) => Accessor<AreaView | undefined> |
An area and its entities. |
useEntityHistory |
(id) => Accessor<EntityHistoryData | undefined> |
Recent state history. |
useEntityStatistics |
(id, options) => Resource<StatisticValue[]> |
Long-term statistics; resource exposes .loading / .error. |
useForecast |
(id) => Accessor<WeatherForecastsData | undefined> |
Weather forecast for a weather entity. |
useCamera |
(id) => { stream: Accessor<CameraStreamData | null>; refresh: () => void } |
Reactive camera stream. |
useCalendarEvents |
(id, options?: { daysBehind?: number; daysAhead?: number }) => Accessor<CalendarEventsData | undefined> |
Live events for a calendar entity, pushed by Home Assistant. Window defaults to 7 days behind and 35 ahead. Data carries events, loading, error and the subscribed start/end. Since SDK 1.10.0. |
useStore |
<T>(selector: (s) => T) => Accessor<T> |
Direct store selector. Escape hatch. |
byDomain |
Accessor<Record<string, string[]>> |
All entity ids grouped by domain. |
Connection & locale hooks
| Export | Signature | What it is |
|---|---|---|
useConnection |
() => { status; isConnected } (accessors) |
Live connection status. |
useHassConfig |
() => Accessor<HassConfig | null> |
Home Assistant config. |
useUnitSystem |
() => Accessor<HassUnitSystem | null> |
Unit system. |
useTemperatureUnit |
() => Accessor<string> |
e.g. "°C". |
useLocale |
() => Accessor<string> |
BCP 47 locale. |
useCurrency |
() => Accessor<string> |
ISO 4217 currency. |
Services
function useService(): {
callService: ServiceCallFn;
turnOn: (entityId: string | string[], data?: Record<string, unknown>) => Promise<void>;
turnOff: (entityId: string | string[], data?: Record<string, unknown>) => Promise<void>;
toggle: (entityId: string | string[], data?: Record<string, unknown>) => Promise<void>;
}
type ServiceCallFn = (
domain: string,
service: string,
data?: Record<string, unknown>,
target?: { entity_id?: string | string[] },
options?: { returnResponse?: boolean }
) => Promise<unknown>;
Host-mounted widgets route these through the host’s capability check (a control grant is required); preview/tests fall back to direct sync-layer calls. useTurnOn(), useTurnOff(), and useToggle() each return just the one shortcut.
Pass { returnResponse: true } for services that return data, such as todo.get_items; the promise then resolves to Home Assistant’s service response instead of undefined. Only set it for services that support a response, since HA errors otherwise. The capability check is unchanged. Since SDK 1.10.0, and the host forwards the response from Dash 1.1.0-beta.5 onward — on older hosts the call still runs but resolves undefined.
Entity utilities
| Export | Signature | What it is |
|---|---|---|
isEntityActive |
(entity: EntityView | null | undefined) => boolean |
true for active states. |
getEntityAttribute |
<T>(entity, name: string, fallback?: T) => T | undefined |
Read one attribute with a fallback. |
hassMediaUrl |
(path: string | null | undefined) => string | undefined |
Resolve an HA media path (album art, camera image) to a loadable URL. See Media URLs. |
countActiveEntities |
(entities: EntityView[]) => number |
Count active entities. |
calculateLightGroup |
(entities: EntityView[], allEntitiesMode?= false) => LightGroupResult |
Aggregate on/off, brightness, color across a group. |
calculateSensorGroup |
(entities: EntityView[], groupType?: SensorGroupType = "mean", ignoreNonNumeric?= true) => SensorGroupResult |
Aggregate numeric sensors. |
LightGroupResult: isGroup, state, isOn, isUnavailable, brightness (0–255), brightnessPercent (0–100), color, onCount, totalCount, description.
SensorGroupResult: isGroup, state, numericValue (number \| null), isUnavailable, unit?, description, memberValues?.
SensorGroupType: "min" \| "max" \| "mean" \| "median" \| "sum" \| "last" \| "range" \| "product" \| "std_dev".
Media URLs
Home Assistant exposes media references (album art entity_picture, camera proxy paths) as origin-relative paths like /api/media_player_proxy/...?token=..., which a bare <img src> resolves against the dashboard’s own origin and fails to load. hassMediaUrl resolves them through the host’s same-origin media proxy, so the image loads both at home and over remote access (no extra permissions, the path keeps Home Assistant’s own signed token).
import { hassMediaUrl, useEntity, getEntityAttribute } from "@glasshome/widget-sdk";
const entity = useEntity(entityId);
const albumArt = () => {
const e = entity();
if (!e) return undefined;
return hassMediaUrl(getEntityAttribute<string>(e, "entity_picture"));
};
<Show when={albumArt()}>
<img src={albumArt()} alt="" />
</Show>;
- Absolute
http(s)://URLs pass through unchanged. - Missing or empty input returns
undefined, so<Show when={albumArt()}>handles “nothing playing” for free. - No external service (cover-art API etc.) is needed for media Home Assistant already serves: read
entity_pictureand wrap it. - Available since widget-sdk 1.3.0.
Theming & charts
| Export | Signature | What it is |
|---|---|---|
isDark |
() => boolean |
Current theme as a boolean. |
injectTokens |
(root?: Document | ShadowRoot) => void |
Attach SDK CSS tokens to a root the host did not set up. |
svgColors |
Record<SvgColorKey, { solid; stroke; fill }> |
Energy chart colors. Keys: solar, grid, battery, ev, home, positive, negative. See Charts. |
ToneSchema |
Zod enum | Validates a Tone. |
monotoneCubicPath |
(points) => string |
Smooth SVG path string from a list of points. |
Tone: "success" \| "warning" \| "danger" \| "info" \| "neutral" \| "accent".
Validation re-exports
Convenience re-exports also available from /schemas.
| Export | Kind | What it is |
|---|---|---|
WidgetManifestSchema |
Zod schema | Validate a complete widget manifest. |
formatSchemaError |
(err) => string |
Turn a Zod error into a readable message. |
Deprecation registry
The single source of truth for deprecated APIs, driving warn-once runtime notices, the CLI lint, and the deprecation docs table.
| Export | Kind | What it is |
|---|---|---|
deprecations |
DeprecationEntry[] |
The registry of deprecated APIs and their replacements. |
formatDeprecation |
(entry: DeprecationEntry) => string |
Render one entry as a readable warning message. |
DeprecationEntry |
type | One deprecation record. |
Lower-level data exports
Building blocks under the hooks; most widgets never touch these.
| Export | Kind | What it is |
|---|---|---|
state |
Store<GlassHomeState> |
The raw reactive store. |
getForecasts |
function | Imperative forecast fetch. |
trackEntityHistory / untrackEntityHistory |
(entityId) => void |
Start / stop history tracking for an entity. |
getStream |
function | Camera stream descriptor. |
getWebRtcClientConfig |
function | WebRTC client config for camera streaming. |
startWebRtcSession |
function | Begin a WebRTC camera session. |
sendWebRtcCandidate |
function | Send an ICE candidate during a session. |
Types (main entry)
interface WidgetDefinition<C = Record<string, unknown>> {
manifest: WidgetManifest;
configSchema?: ZodType<C, unknown>;
migrate?: (config: Record<string, unknown>, fromConfigVersion: number) => Record<string, unknown>;
component: (props: { config: C }) => any;
}
interface WidgetManifest {
name: string;
description?: string;
minSize: GridSize; // { w, h }
maxSize: GridSize;
defaultSize?: GridSize;
sdkVersion: string;
icon?: string;
capabilities?: CapabilityGrant[];
configVersion?: number;
schema?: object; // auto-populated from configSchema; do not set
defaultConfig?: Record<string, unknown>; // auto-populated; do not set
cssUrl?: string; // set by the build
}
interface GridSize { w: number; h: number }
Other exported types: WidgetContext, ReactiveWidgetContext, EntityView (see the shape), AreaView, WidgetVariantConfig, WidgetStyles, Tone, SvgColorKey, GestureHandlers, AggregationPreset, SensorGroupType, LightGroupResult, SensorGroupResult, UseWidgetEntityGroupOptions, UseWidgetEntityGroupResult, WidgetDialogProps, WidgetDialogTab, WidgetDialogReturn, ServiceCallFn.
@glasshome/widget-sdk/schemas
Zod schemas re-exported from the canonical @glasshome/widget-contract package, the single source of truth shared by the SDK, CLI, Hub, and Dash, so the same definitions validate a manifest everywhere.
| Export | Kind | What it is |
|---|---|---|
widgetManifestSchema (alias WidgetManifestSchema) |
Zod schema | Validate a complete widget manifest. |
publishManifestSchema |
Zod schema | Validate the manifest subset sent at publish time. |
capabilitiesSchema |
Zod schema | Validate a capabilities array. |
capabilityGrantSchema |
Zod schema | Validate one { domain, access } grant. |
GridSizeSchema |
Zod schema | Validate { w, h }. |
parseGridSize / serializeGridSize |
functions | Convert grid size to/from the stored JSON form. |
PublishRequestSchema / PublishConfirmSchema / PublishBodySchema |
Zod schemas | Publish-flow request bodies. |
formatSchemaError |
(err) => string |
Turn a Zod error into a readable message. |
CapabilityGrant |
type | One capability grant. |
@glasshome/widget-sdk/vite
Build plugins and helpers for vite.config.ts. The scaffolded config wires these; reach for them only in custom setups. See Build tooling.
| Export | Signature | What it is |
|---|---|---|
glasshomeWidget |
(options?: GlasshomeWidgetOptions) => Plugin[] |
Single-widget plugin: dev preview + library build. |
glasshomeWidgets |
(options?: GlasshomeWidgetsOptions) => Plugin[] |
Multi-widget plugin: per-widget builds + registry.json. |
buildWidgets |
(options?: BuildWidgetsOptions) => Promise<void> |
Programmatic build of all widgets (used by bun widget build). |
discoverWidgets |
(srcDir: string) => DiscoveredWidget[] |
Scan src/ for subdirs with index.tsx + manifest.json. |
generateRegistry |
(srcDir: string, outDir: string) => void |
Write a registry.json from discovered manifests. |
isWidgetExternal |
(id: string) => boolean |
true for host-provided packages (kept out of the bundle). |
Option shapes (all exported):
GlasshomeWidgetsOptions:{ srcDir?, outDir? }only.BuildWidgetsOptions:{ srcDir?, outDir?, plugins?, viteConfig?, only? }(plugins,viteConfig, andonlyarebuildWidgets-only;glasshomeWidgetsdoes not accept them).GlasshomeWidgetOptions:{ entry? }(defaults to"src/index.tsx").
Dialog UI components (from @glasshome/ui)
WidgetDialog takes its chrome as props. These are not SDK exports; import them from @glasshome/ui/solid and pass them in (keep @glasshome/ui as a dependency). See WidgetDialog component.
ResponsiveDialog, ResponsiveDialogContent, ResponsiveDialogHeader, ResponsiveDialogTitle, ResponsiveDialogDescription, Button, SchemaForm (SchemaForm is required only when you use configSchema).