Capabilities & Permissions
A widget runs sandboxed: an isolated shadow root, no Home Assistant connection or token, no network beyond Home Assistant and the Hub. To read or control anything in the home, it declares the access it needs in its manifest. The user approves that list at install, and the host enforces it on every call.
Access is deny-by-default: anything you don’t declare is blocked at runtime.
This is the developer’s view. For the homeowner-facing explanation of the sandbox, see Widget Security.
How access works
Home Assistant interaction goes only through the SDK’s data and service hooks: importing @glasshome/sync-layer directly fails the build, and fetch/WebSocket to Home Assistant is blocked. The host checks every call against your declared capabilities before touching Home Assistant.
Reads (entity state, history, forecasts) require a read grant for the domain; service calls require control.
Declaring capabilities
- 1
List what the widget needs, minimally
Decide which Home Assistant domains the widget touches and whether it only shows them (
read) or also operates them (control). Ask for the least that works. - 2
Add a capabilities array to the manifest
Each grant is
{ domain, access }, plus optional narrowing:// manifest.json "capabilities": [ { "domain": "light", "access": "control" }, { "domain": "sensor", "access": "read" } ]The same array goes in the inline
manifestpassed todefineWidget. A widget that touches no Home Assistant data (a clock, say) declares an empty array:"capabilities": []Targeting SDK 1.x without a
capabilitiesarray is rejected at publish. A manifest may declare at most 32 grants. - 3
Use the data layer
Read and control through the SDK hooks:
import { useEntity, useService } from "@glasshome/widget-sdk"; const light = useEntity(() => props.config.entityId); // needs read/control on "light" const { toggle } = useService(); // toggle(props.config.entityId) // needs control on "light"
Access levels
access |
Grants | Use it for |
|---|---|---|
read |
Reading state, attributes, history, forecasts for the domain | Widgets that only display |
control |
Read and calling services on the domain | Widgets that operate devices |
control implies read on the same domain, so you never need both.
Narrowing a grant
A grant covers a whole domain by default; tighten it when the widget needs only part of one.
To specific entities
entities lists entity-id patterns. The domain part stays literal and * globs only the object id, so a pattern can never widen a grant past its domain.
{ "domain": "light", "access": "control", "entities": ["light.living_*", "light.kitchen"] }
An entity-scoped grant cannot reach domain-wide services (a service call with no entity target). Leave entities off if the widget needs the whole domain.
To specific services
services restricts a control grant to named services; calls outside the list are rejected.
{ "domain": "media_player", "access": "control", "services": ["media_play", "media_pause"] }
entities and services must be non-empty when present.
What the user approves
The install consent screen renders each grant as a plain-language sentence, generated from the same object the host enforces, so the prompt cannot drift from what is allowed:
| Grant | Shown to the user |
|---|---|
{ domain: "light", access: "control" } |
Control your lights |
{ domain: "sensor", access: "read" } |
Read your sensors |
{ domain: "light", access: "control", entities: ["light.living_*"] } |
Control your lights (light.living_*) |
{ domain: "media_player", access: "control", services: ["media_play","media_pause"] } |
Control your media players — only: media play, media pause |
Enforcement and updates
- Deny-by-default. Every read and service call is checked against your grants at runtime; anything unmatched is blocked and the user is notified. Widget code cannot opt out.
- Widening re-prompts. An update asking for more than the user approved (a new domain, broader access, removed narrowing) must be re-approved before it runs. Narrowing or unchanged grants install silently.
- Verified on publish. The Hub validates your capability grammar and the bundle hash before a version goes live.