# GlassHome (full docs) > Concatenated, machine-readable bundle of every GlassHome doc page. One fetch. Frontmatter preserved per page so you can split by `---`. Generated: 2026-06-13T23:35:29.781Z Source site: https://glasshome.app/docs --- title: "FAQ" description: "Frequently asked questions about GlassHome. Local-first, Home Assistant, hardware, accounts, privacy, and licensing." slug: faq canonical: https://glasshome.app/docs/faq section: "Support" updated: 2026-06-09 --- # FAQ Short answers. Each links to deeper docs. ## Does GlassHome replace Home Assistant? No. Home Assistant still runs your automations and integrations. GlassHome is a UI in front of it. See [Concepts](/docs/concepts). ## Does it work without internet? Yes. Dash and HA talk on your LAN. Internet is only required to (a) install community widgets the first time, or (b) use the optional managed [tunnel](/docs/remote-access). ## Do I need a Hub account? Only if you want to publish widgets, manage [organizations](/docs/organizations), or use the managed remote-access tunnel. Dash itself runs without one. ## What data does Hub collect? Hub only sees what you intentionally send: published widget bundles, organization membership, and tunnel provisioning metadata. Your dashboard layouts, HA URL, and entity state never reach Hub. See [Concepts → Data flow](/docs/concepts#data-flow). ## What hardware do I need? Any 64-bit CPU with SSE4.2 (Intel 2008+, AMD 2011+) or ARMv8 (Pi 4/5, Apple Silicon). Pi 3 is too slow. VMs need a modern CPU profile, see [Troubleshooting](/docs/troubleshooting#illegal-instruction). ## Can I run it on the same machine as Home Assistant? Yes. Most users do. Just make sure ports don't collide (HA is `8123`, GlassHome is `3123` by default). ## Does the addon show up in the Home Assistant sidebar (ingress)? No. GlassHome runs as its own web app on port `3123`, not through HA ingress. After starting the addon, open `http://:3123` in a browser. This keeps the dashboard full-screen and independent of the HA frontend. See the [Addon guide](/docs/addon). ## How do I access my dashboard away from home? Today, use a VPN (WireGuard, Tailscale) so your device joins the home network and reaches Dash like it's local. A built-in managed tunnel and reverse-proxy support are in the works. See [Remote Access](/docs/remote-access). ## Does it support YAML config? No. Everything is configured in the UI and stored in SQLite. That's intentional; see [What GlassHome is not](/docs). ## Can I sync dashboards between devices? Every device pointed at the same Dash instance sees the same dashboards. There's no cloud sync of layouts between separate Dash installs. ## Is it open source? The widget SDK and CLI are open source. The dashboard is source-available; see the repo for license terms. ## How do widgets get reviewed? Community widgets publish immediately and are flagged with a Community badge. Users see a consent prompt before install. Official widgets go through GlassHome review. See [Publishing → Trust Badges](/docs/widget-publishing#trust-badges). ## Where do I report a bug or ask for a feature? [Discord](https://discord.gg/FJYdeDmrzv) for chat and bug reports. Template in [Bug Reports](/docs/bug-reports). --- ## Pricing and Pro ### Is GlassHome free? The core app (Dash) is free and stays free. [Pro](/docs/pro) is a one-time purchase that adds community widgets and theming on top. No account or payment is needed to use Dash locally. ### What does Pro cost? Pro is **$19.99** (Early Bird offer, available until end of June 2026), then **$39.99** standing. It is a one-time purchase, not a subscription. See the [pricing page](/pricing) for the current offer and any active countdown. ### Is it a subscription? No. Pro is a one-time purchase via Polar. You pay once and the license is yours permanently. All v1 updates are included. See [Pro](/docs/pro). ### What do I get with Pro? Pro adds two things on top of the free tier: **community widgets** (install widgets from the Hub registry beyond the official `@glasshome` set) and **theming** (the in-app theme editor, custom backgrounds, and theme upload). All 7 presets are free, and custom themes already on your Dash stay usable without Pro. See the full comparison at [Pro](/docs/pro). ### Does Pro work on multiple devices? Yes. Pro is tied to your Hub account, not a device. You can link and unlink Dash instances whenever you like, and any Dash you link gets the Pro entitlement. Multiple devices in the same home pointing at the same Dash instance all benefit without extra licenses. ### What is the refund policy? Reach out at [contact@glasshome.app](mailto:contact@glasshome.app) and I will sort it out. See [Pro → Managing your license](/docs/pro#managing-your-license) for details. --- title: "Glossary" description: "A-Z definitions of GlassHome terminology. Quick lookup for terms used across the docs." slug: glossary canonical: https://glasshome.app/docs/glossary section: "Reference" updated: 2026-06-09 --- # Glossary ## A **Account dashboard.** The page at [glasshome.app/dashboard](/dashboard) where you manage your Hub account, view your Pro license card, and see your member number. **Addon.** A packaging of GlassHome Dash for Home Assistant OS. Installed via the HA Add-on Store. See [Addon](/docs/addon). **Area.** Home Assistant's grouping of devices/entities by room. Some widgets bind to an area instead of a single entity. ## B **Breakpoint.** Layout slot keyed by screen width. `lg` (>=1024px), `md` (>=768px), `sm` (\<768px). Each dashboard stores one layout per breakpoint. See [Layouts](/docs/layouts). **Bundle.** Compiled JS file produced by `bun widget build`, one per widget, plus a `registry.json`. ## C **Config dialog.** The settings panel that opens when you tap the gear or edit affordance on a widget in edit mode. Fields are auto-generated from the widget's Zod schema. Changes save instantly. **configVersion.** Integer on a widget manifest. Incremented on breaking config changes. Drives [migrations](/docs/widget-migrations). **Cloudflared.** Cloudflare's tunnel client, launched as a child process by GlassHome to expose Dash via [Remote Access](/docs/remote-access). Feature currently in progress. ## D **Dash.** The local dashboard app (the `glasshome-dash` container) that runs on your hardware and connects directly to Home Assistant. This is the GlassHome UI you interact with day to day. **Dashboard.** A named, individually-laid-out screen inside Dash. Multiple dashboards live side by side and are switched via the dock at the bottom. Each has its own widget set and independent layout per breakpoint. **defineWidget.** SDK entry point. Wraps a manifest, optional Zod schema, and SolidJS component into a registerable widget. See [Widget SDK](/docs/widget-sdk). **Demo Mode.** A simulated HA backend baked into the dashboard. Lets you evaluate GlassHome without a real HA instance. See [Connecting](/docs/connecting#connection-methods). **Detail dialog.** The overlay that opens when you long-press (500 ms) a widget in live mode. Hosts fine controls (sliders, advanced actions) that don't fit on the tile itself. ## E **Early Bird.** The time-limited introductory price for Pro ($19.99, available until end of June 2026). After the offer ends, the standing price ($39.99) applies. See [Pro](/docs/pro). **Edit mode.** The state Dash enters when you tap the pencil icon in the header. A faint grid overlay appears, widgets show resize grips and X buttons, and gestures shift from device control to layout editing. See [Editing](/docs/editing). **Entity.** A Home Assistant primitive (`light.kitchen`, `sensor.temp`). Widgets bind to one or more entities. ## H **Hub.** The GlassHome cloud service at glasshome.app. Hosts the widget registry, user accounts, organizations, and tunnel provisioning. Never sees dashboard data. **HAOS.** Home Assistant Operating System. Includes Supervisor and addon support. ## L **License / member number.** Proof of a Pro purchase. Your sequential member number is assigned once at checkout and never changes. Shown on your [account dashboard](/dashboard). ## M **Manifest.** `manifest.json` (or inline metadata in `defineWidget`). Declares widget name, icon, size limits, SDK version, configVersion. **Migration.** A function on a widget that transforms old saved config into the current shape when `configVersion` increases. ## O **Organization.** A shared publishing scope (`@org-slug`). Multiple members publish under one namespace. See [Organizations](/docs/organizations). ## P **Polar.** The payment platform GlassHome uses for Pro checkout. Handles billing and receipt emails. See [Pro → How to buy](/docs/pro#how-to-buy). **Pro.** The one-time purchase that adds community widgets and theming (the theme editor, custom backgrounds, theme upload) to Dash. Not a subscription. See [Pro](/docs/pro). ## R **Registry.** Hub's catalog of published widgets. The dashboard queries it during widget browse/install. Bundles served from `cdn.glasshome.app`. ## S **Scope.** Publishing namespace. `@username` (personal) or `@org-slug` (organization). Widget IDs look like `@scope/widget-name`. **SDK.** `@glasshome/widget-sdk`. Provides `defineWidget`, `Widget` components, hooks, entity helpers, theming utilities. **SDK version.** Range string in a manifest (e.g. `^0.5.0`) declaring which SDK majors the widget supports. Dash rejects widgets built against an unsupported SDK. ## T **Theme preset.** One of the 7 named built-in themes (Midnight Glass, Sunrise Studio, Forest Zen, Lavender Dreams, Coral Reef, Monochrome Pro, Ocean Breeze). Each ships with light and dark variants. Switching presets is free; editing or uploading a custom theme requires Pro. See [Themes](/docs/themes). **Trust badge.** `Official` (verified, installs without prompt) or `Community` (user-published, asks for consent). Shown on every widget in the picker. **Tunnel.** A `cloudflared` process that will expose Dash via Cloudflare's edge once the feature ships. Provisioned through Hub. See [Remote Access](/docs/remote-access). ## W **Widget.** A tile on a dashboard bound to one or more entities. SolidJS component + Zod config. **Widget CLI.** `@glasshome/widget-cli`. Scaffolds, builds, validates, and publishes widgets. See [Widget CLI](/docs/widget-cli). **WebSocket (WS).** The protocol Dash uses to talk to Home Assistant. Direct, low-latency, no cloud relay. --- title: "Documentation" description: "GlassHome is a modern, local-first dashboard for Home Assistant. Install in minutes, customize without YAML." slug: index canonical: https://glasshome.app/docs section: "Getting started" updated: 2026-06-09 --- # GlassHome Documentation A modern dashboard for Home Assistant. Connects directly to your HA instance over WebSocket. No cloud relay, no telemetry, no YAML required. ## Start here ### [Quickstart](/docs/quickstart) Install, connect, and add your first widget in 5 minutes. ### [Concepts](/docs/concepts) Hub, dashboard, addon, widget. The mental model in one page. ### [Installation](/docs/installation) Pick a deployment method: HA addon, Docker, or container. ## Choose your path ### [I want to use it](/docs/widgets) Add widgets, arrange dashboards, pick themes. ### [I want to build for it](/docs/widget-development) Build and publish custom widgets with the SDK and CLI. ## Common tasks ### [Connect to Home Assistant](/docs/connecting) OAuth flow, demo mode, URL gotchas. ### [Edit a dashboard](/docs/editing) Add, move, resize, remove. Desktop and touch gestures. ### [Access from anywhere](/docs/remote-access) VPN works today; managed tunnel in progress. ### [Theme it](/docs/themes) 7 presets or custom palette, light and dark. ### [Pro](/docs/pro) One-time purchase. Unlocks community widgets and the theme editor. ### [Something broke](/docs/troubleshooting) Look up by error message or symptom. ## What GlassHome is not - **Not a replacement for Home Assistant.** HA still runs your automations, integrations, and devices. GlassHome is the UI layer in front of it. - **Not a cloud service.** Dash runs on your hardware. Your Hub account exists only to publish widgets and (optionally) provision a tunnel. - **Not config-as-code.** Everything is configured in the UI. No YAML, no restart-to-apply. > **Need help?:** Join the Discord for live support, or file a [bug report](/docs/bug-reports). --- title: "Installation" description: "Install GlassHome as a Home Assistant addon, Docker Compose service, or standalone container. CPU requirements and VM gotchas covered." slug: installation canonical: https://glasshome.app/docs/installation section: "Installation" updated: 2026-06-09 --- # Installation All install methods deploy the same app. Pick by environment, not by feature set. ## Pick a method | Your setup | Use | |-------------------------------------|-----------------------------------------------------------| | Home Assistant OS or Supervised | [HA Addon](/docs/addon) (recommended) | | HA Container / Core | [Docker Compose](/docs/docker#docker-compose) | | Any other machine with Docker | [Direct Container](/docs/docker#direct-container) | | Bare metal without Docker | Not supported. Run Docker or use HA OS. | ## Requirements - **A running Home Assistant.** Any install method, reachable on your network. Any recent Home Assistant release works. - **Docker** (unless using the HA addon), or **HAOS/Supervised with Supervisor** for the addon. - **Modern browser** to use Dash: Chrome, Firefox, Safari, or Edge. ## Supported CPUs GlassHome ships multi-arch images. Docker picks the right build automatically. ### x86-64 (amd64) Requires SSE4.2. Intel Nehalem (2008+) or AMD Bulldozer (2011+) and newer. Almost any PC from the last decade. ### ARM64 (aarch64) Raspberry Pi 4 and 5, Apple Silicon, most modern ARMv8 SBCs. Pi 3 and earlier are not supported. > **Running in a VM? Read this.:** Many hypervisors expose a Pentium 4-era CPU profile by default that hides SSE4.2 from the guest, even when the host CPU is modern. GlassHome will crash with `signal 4 / illegal instruction`. Switch the guest CPU to `host` (Proxmox), disable EVC (ESXi), or turn off processor compatibility mode (Hyper-V). Full fix: [Troubleshooting → Illegal Instruction](/docs/troubleshooting#illegal-instruction). ## After installing 1. Open `http://:3123`. 2. Run the setup wizard. See [Connecting to Home Assistant](/docs/connecting). 3. Add your first widget. See [Quickstart](/docs/quickstart). ## Uninstalling - **Addon:** Uninstall from the Add-on Store. Optionally check "remove data". - **Docker:** `docker rm -f glasshome && docker volume rm glasshome_data` (this deletes your dashboards). --- title: "Organizations" description: "Create and manage organizations on GlassHome Hub to publish widgets under a shared team scope." slug: organizations canonical: https://glasshome.app/docs/organizations section: "Hub" updated: 2026-06-09 --- # Organizations Organizations let teams publish widgets under a shared `@org-slug` scope instead of personal scopes. Multiple members can collaborate and publish widgets that appear under your organization's namespace. ## What are Organizations When you publish a widget as an individual, it appears under your personal scope (e.g., `@username/widget-name`). Organizations give teams a shared publishing namespace, for example `@my-team/widget-name`. Any member with the right permissions can publish widgets under the organization scope. Each account can create one organization. The limit is per-account and is enforced at creation time. ## Creating an Organization ### Go to Organizations Dashboard > Organizations tab ### Create Organization Click the Create Organization button ### Name your org Slug is auto-derived from the name The organization name is used to derive a URL-safe slug automatically; spaces and special characters are replaced with hyphens. The slug becomes your publishing scope (e.g., `@my-team`) and **cannot be changed after creation**. ## Members and Roles Organizations have three roles that control what each member can do: ### Owner Full access. Can manage members, update settings, publish widgets, and delete the organization. ### Admin Can invite and remove members, manage member roles, and publish widgets under the org scope. ### Member Can view organization details. Cannot publish widgets under the org scope (only Owner and Admin can publish). ### Invitations To invite a member, go to the org detail page and click **Invite Member** in the Members tab. Enter their email address and select a role. The invitation is sent to that email address. The invitee **must have a GlassHome Hub account** with a matching email address to accept; there is no open sign-up path from an invitation link. Invitations expire after **48 hours**. To accept, the invitee visits their Hub account and accepts from the pending invitations list. Owners and Admins can change member roles or remove members from the org detail page. ### Who can update existing widgets Once a widget is published under an org scope, any **Owner or Admin** can re-publish (update) it by running `bun widget publish` with the org scope. Members with the `member` role cannot publish. See [Publishing Widgets](/docs/widget-publishing). ## Ownership transfer There is no automated ownership transfer UI. If you need to transfer org ownership to another member, contact me directly. ## Publishing under an Organization When you run `bun widget publish`, the CLI prompts you to select a publishing scope if you belong to any organizations: ``` ? Select publishing scope: ❯ @your-username (personal) @my-team (organization) ``` Widgets published under an organization scope appear with the org's scope tag in the Widget Browser (e.g., `@my-team/widget-name`). ## Managing Organizations The **Settings** tab in the org detail page lets Owners and Admins rename the organization. The slug is read-only after creation. ### Danger Zone - **Leave Organization.** Members and Admins can leave an organization. You will lose publishing access under the org scope. - **Delete Organization.** Only the Owner can delete an organization. Widgets already published under the org scope will remain available in the Widget Browser but cannot be updated. --- title: "Troubleshooting" description: "Look up GlassHome errors and fixes by symptom or message. Illegal instruction, container won't start, CORS, connection drops, and more." slug: troubleshooting canonical: https://glasshome.app/docs/troubleshooting section: "Troubleshooting" updated: 2026-06-09 --- # Troubleshooting Find your symptom, follow the fix. If nothing matches, file a [bug report](/docs/bug-reports). ## Quick index - [Container or addon crashes on start with `signal 4` / illegal instruction](#illegal-instruction) - [Setup wizard rejects the HA URL](#bad-ha-url) - [Connected, but entities never load](#no-entities) - [Dashboard loads but blank / errors in browser console](#blank-ui) - [Remote-access tunnel won't connect](#tunnel) - [Widget install fails / shows "incompatible SDK"](#widget-sdk) - [Layout / theme reset after update](#data-loss) - [Widget shows "Unavailable"](#widget-unavailable) - [HA login loop / OAuth fails](#oauth-loop) - [Theme or background not applying](#theme-not-applying) ---

Container crashes with "Illegal Instruction" (signal 4)

### Symptoms Container or addon dies instantly. Logs show `signal 4`, `SIGILL`, `trap invalid opcode`, or `Illegal instruction`. ### Cause CPU does not expose SSE4.2. GlassHome requires Intel Nehalem (2008+) or AMD Bulldozer (2011+). On modern hardware, the actual cause is almost always a hypervisor hiding instructions from the guest. ### Fix: virtualized host By default many hypervisors expose a Pentium 4 era CPU profile for compatibility. Switch the guest CPU to one that exposes the host's real features. **Proxmox** 1. Shut down the VM running HA / GlassHome. 2. Hardware > Processor > Type: change `kvm64` to `host`. 3. Start the VM. In a Proxmox cluster: use `x86-64-v3` instead of `host` to keep live-migration working between v3-capable nodes. **VMware ESXi / Workstation.** Disable EVC, or raise the EVC baseline to one that includes SSE4.2. **Hyper-V.** Disable processor compatibility mode on the VM. **VirtualBox.** Enable Nested VT-x/AMD-V. Avoid legacy CPU profiles. ### Fix: bare metal If GlassHome runs directly on hardware (no hypervisor), the CPU itself is below the minimum. No software fix. Move to a Raspberry Pi 4/5 or any PC from the last decade. ---

Setup wizard rejects the HA URL

The URL must include protocol and the port HA actually serves on. Verify by opening it in a browser tab. If you can't reach HA in your browser, the dashboard can't either. Common mistakes: - Missing `http://` or `https://`. - Wrong port (HA's UI port, not the API). - HTTPS on dashboard side but plain HTTP on HA (or vice versa). They must match. - `localhost` from inside a container points at the container, not your machine. Use the LAN IP or `host.docker.internal`. See [Connecting → Entering the URL](/docs/connecting#entering-the-url). ---

Connected, but no entities load

- The HA user you authorized as may have no entity permissions. Re-authorize with an admin user. - HA's `auth` integration may be using a custom provider that strips refresh tokens. Confirm HA's `configuration.yaml` doesn't disable refresh tokens. - If entities load partially, you've likely hit an HA WS rate limit during initial load. Refresh. ---

Dashboard loads blank

Open browser devtools (F12) > Console. Look for red errors: - **`Failed to fetch dynamically imported module`**: a widget bundle 404'd. Likely an installed community widget was unpublished. Remove the widget from your layout. - **`SecurityError: Failed to construct WebSocket`**: HTTPS dashboard trying to reach HTTP HA, or vice versa. Match protocols. - **`Refused to connect`**: CSP violation. Usually means you're proxying the dashboard and the proxy strips required headers. Bypass the proxy to confirm. ---

Remote-access tunnel won't connect

> **Feature in progress:** The built-in managed tunnel is still under active development and not yet available. These steps will apply once it ships. - Click **Settings > Remote Access > Retry**. - Confirm the host can reach `*.cloudflareaccess.com` and `github.com` (the `cloudflared` binary is fetched from GitHub Releases on first enable). - Logs: `docker logs glasshome` (or HA addon log). Look for `cloudflared` lines. More: [Remote Access](/docs/remote-access). ---

Widget install fails with "incompatible SDK"

The widget was built against a newer `@glasshome/widget-sdk` than the dashboard supports. Update the dashboard (`docker compose pull && docker compose up -d`, or addon update). If you're the widget author, lower the `sdkVersion` range in the manifest or publish a build against the older SDK. ---

Dashboards or theme disappeared after update

Data lives in the Docker volume `glasshome_data` (mounted at `/data`). If it was missing on restart, the dashboard initializes a fresh DB. - Confirm the volume is mounted: `docker inspect glasshome | grep Mounts -A 10`. - If you migrated hosts, copy `/data` from the old host before first start on the new one. - HA addon stores data in addon-managed storage. Re-installing the addon with "remove data" unchecked preserves dashboards. If your data really is gone, file a [bug report](/docs/bug-reports) with logs from the failing start. ---

Widget shows "Unavailable"

The entity the widget is bound to is missing or has been renamed in Home Assistant. - Check the entity still exists in HA (Settings > Devices & Services > Entities). - If it was renamed or removed, open the widget's config dialog in edit mode and rebind it to the correct entity. ---

HA login loop / OAuth fails

If you keep being redirected back to the HA login page or authorization never completes: - Confirm the HA URL you entered is reachable from the browser (not just from the server). Open it in a new tab. - Check that the URL uses the correct protocol (`http://` vs `https://`). A mismatch causes silent CORS failures. - Make sure popups are not blocked. The OAuth flow opens in a popup or redirect depending on the browser. See [Connecting to Home Assistant](/docs/connecting) for URL format requirements and common pitfalls. ---

Theme or background not applying

- Hard-refresh the page (Ctrl+Shift+R / Cmd+Shift+R) to clear any cached assets. - Confirm you are applying the change in **Settings > Theme** and that the save completes (changes persist immediately; there is no separate save button). - If a custom theme or uploaded background is not showing, confirm the file format is supported and try re-uploading. Creating or editing custom themes and uploading backgrounds requires [Pro](/docs/pro); applying an existing custom theme does not. - If the issue persists after a refresh, check the browser console for errors and file a [bug report](/docs/bug-reports). --- title: "Widget Development" description: "Build custom GlassHome widgets with SolidJS and the widget SDK. Scaffold, develop with hot-reload, validate, and publish." slug: widget-development canonical: https://glasshome.app/docs/widget-development section: "Widget Development" updated: 2026-06-09 --- # Widget Development A widget is a SolidJS component + a Zod config schema, wrapped with `defineWidget`. The dashboard renders the component, auto-generates the settings form from the schema, and feeds in entity state. ### Scaffold `bunx @glasshome/widget-cli@latest` (no args). Creates a project and your first widget. ### Hot-reload `bun widget connect ` rebuilds on save and re-uploads to a running dashboard. ### Publish `bun widget publish` to Hub. Pick personal or organization scope. The `bun widget ` form works inside scaffolded projects (they pin the CLI in `devDependencies` and ship a `widget` script alias to `glasshome-widget`). Outside a project, use `bunx @glasshome/widget-cli@latest `. See [Widget CLI](/docs/widget-cli) for the full command list and details. ## Prerequisites | Tool | Version | Notes | |------|---------|-------| | Bun | 1.0+ | CLI `engines` field requires `bun >=1.0.0` | | Node.js | 18+ | Required by `@glasshome/widget-sdk` (`engines.node >= 18`); Bun's bundled Node compatibility layer satisfies this | Scaffolded projects ship a `.mise.toml` pinning the versions. Install [mise](https://mise.jdx.dev) and run `mise install`. ## Quickstart 1. ```bash bunx @glasshome/widget-cli@latest cd ``` Generates `package.json`, `vite.config.ts`, `tsconfig.json`, the `widget` script alias, and your first widget (prompts for name + description). 2. `create` already scaffolds your first widget. To add another: ```bash bun widget add ``` Prompts for name and description, creates `src//index.tsx` and `src//manifest.json`. 3. ```bash bun widget connect http://homeassistant.local:3123 ``` Builds, runs an OAuth **device-code flow against your local Dash instance** (not Hub), uploads each bundle to the Dash API under the `local` scope, enables dev mode, then watches `src/` and re-uploads on save. Widgets refresh without reloading the page. 4. ```bash bun widget publish ``` `publish` runs `login` automatically the first time. See [Publishing](/docs/widget-publishing). ## Project layout ``` my-widgets/ ├── package.json ├── vite.config.ts ├── tsconfig.json └── src/ ├── my-widget/ │ ├── index.tsx # Component + defineWidget │ └── manifest.json # Widget metadata └── another-widget/ ├── index.tsx └── manifest.json ``` One widget per directory under `src/`. Build outputs one self-contained JS bundle per widget plus a `registry.json` in `dist/`. ## Mental model - The dashboard already handles: rendering, gestures, theming, entity subscriptions, settings forms, layout. - Your widget supplies: how the bound entities are visualized, and what the primary tap action does. - You do *not* write settings UI. Define a Zod schema; the dashboard renders the form. - You do *not* manage WebSocket state. Bind entities via `useWidgetEntityGroup` and the SDK keeps them reactive. ## Versions at a glance | Version type | Where it lives | Who bumps it | What breaks without it | |---|---|---|---| | SDK version (`sdkVersion`) | `manifest.json`, as a semver range (e.g. `^1.0.0`) | `bun widget upgrade` after you update the dep | Dash may refuse to load bundles built against a wildly different SDK | | Widget version (`version`) | `manifest.json` | `--bump` flag on `bun widget publish` | Re-publishing the same version is rejected (409) | | Config version (`configVersion`) | `manifest.json`, integer | You, when config shape has breaking changes | Without a bump, users' saved configs aren't migrated and may fail Zod parse | | CLI minimum (Hub-enforced) | Served by Hub at `/api/widgets/cli-version` | Hub (I bump it on protocol changes) | `login`/`publish` hard-stop if CLI is below the floor | ## Auth flows: connect vs. login Two separate auth flows exist for two separate systems: - **`bun widget connect`** runs an **OAuth device-code flow against your local Dash instance**. A browser opens to the Dash approval page. The token it issues is scoped to that Dash host and stored separately from Hub credentials. - **`bun widget login`** runs an **OAuth PKCE flow against GlassHome Hub** (`glasshome.app`). A browser opens to Hub; the callback lands on `http://127.0.0.1:9274/callback` (or `localhost`). The resulting token is used for `publish`. ## Where to go next ### [Widget SDK reference](/docs/widget-sdk) defineWidget, Widget components, hooks, the HA data layer, entity helpers. ### [Capabilities & Permissions](/docs/widget-capabilities) Declare the Home Assistant access your widget needs and how the user approves it. ### [Styling & Animation](/docs/widget-styling) Per-widget CSS bundles, Tailwind, your own CSS files, theme variables, container queries, animation. ### [CLI reference](/docs/widget-cli) Every `bun widget ` and what it does. ### [Publishing](/docs/widget-publishing) Auth, scopes, versioning, trust badges. ### [Config migrations](/docs/widget-migrations) Handle breaking config changes without breaking users. --- title: "Widgets" description: "Widget types built into Dash, configuring widgets, community widgets from Hub, and building your own." slug: widgets canonical: https://glasshome.app/docs/widgets section: "Dashboards" updated: 2026-06-09 --- # Widgets A widget is a tile bound to one or more Home Assistant entities. Dash ships with widgets for lights, switches, sensors, climate, covers, scenes, and more. Community widgets are installed from Hub. ## Adding a widget Adding widgets is part of edit mode. See [Editing](/docs/editing) for the full step-by-step and gesture reference. ## Configuring a widget Open edit mode and tap the gear or edit affordance on any widget. The config dialog opens with fields auto-generated from the widget's schema: bound entity, display options, size, color, and more. Changes save instantly, no apply button. For the complete gesture reference (including how the config dialog relates to the detail dialog in live mode), see [Editing](/docs/editing). ## Built-in widget types All official `@glasshome` widgets are included in Dash and available to every user for free. | Widget | What it does | |---|---| | Light | Toggle, dim, color-temp. Supports groups and color lights. | | Switch | Toggle any switch or input boolean. | | Sensor | Display a sensor value with configurable unit and precision. | | Climate | Temperature setpoint, HVAC mode, fan mode. | | Cover | Open, close, and position blinds, garage doors, and shutters. | | Scene | Activate a scene with a single tap. | | Area | Area overview with entity grouping and batch controls. | | Batteries | Auto-discover and monitor battery levels across all devices. | | Binary Sensor | Motion, door, occupancy, and other binary sensors. | | Button | Press a button entity. | | Camera | Live camera stream with multi-protocol support. | | Locks | Lock and unlock with status display. | | Blinds | Position slider and tilt control. | ## Community widgets > **Pro required:** Installing community widgets requires a [Pro](/docs/pro) license. Official `@glasshome` widgets are free for everyone. Browse and install community widgets from the widget picker (tap **Get more widgets** at the bottom), or browse the registry at [glasshome.app/#widgets](/#widgets). - **Official** widgets are verified by GlassHome and install without a prompt. - **Community** widgets ask for consent before install (third-party code). The bundle is downloaded once from Hub's CDN, then cached and served locally. ## Build your own Custom widgets are SolidJS components with a Zod config schema. Dash auto-generates the edit form from the schema, so you do not write any settings UI. Start with [Widget Development](/docs/widget-development). --- title: "Quickstart" description: "Install GlassHome, connect it to Home Assistant, and add your first widget in under 5 minutes." slug: quickstart canonical: https://glasshome.app/docs/quickstart section: "Getting started" updated: 2026-06-09 --- # Quickstart End-to-end in under 5 minutes: install, connect, customize. Assumes you already have Home Assistant running. ## Before you start - A reachable Home Assistant URL (e.g. `http://homeassistant.local:8123`). - A device to run GlassHome on. Same machine as HA is fine. CPU requirements: SSE4.2 / ARMv8 ([details](/docs/installation#supported-cpus)). - No HA yet? You can still try demo mode. Skip to step 2 and pick **Demo mode** on the welcome screen. 1. Pick one. Both deploy the same app. **HA OS or Supervised users.** Add the [GlassHome Addon repository](/docs/addon) and install from the Add-on Store. One-click, no Docker needed. **Everyone else.** Run the container: ```bash docker run -d \ --name glasshome \ -p 3123:3123 \ -v glasshome_data:/data \ --restart unless-stopped \ ghcr.io/glasshome/dash:latest ``` 2. Go to `http://:3123` in your browser. The setup wizard loads. For addon installs, `` is your Home Assistant machine's IP address (e.g. `http://192.168.1.100:3123`). No HA yet? Click **Demo mode** on the welcome screen to explore with simulated data. 3. Enter your HA URL (with `http://` or `https://` and the port). Authorize in the HA popup. The wizard hands back to Dash automatically. Stuck? See [Connecting](/docs/connecting#troubleshooting). 4. 1. Tap the **pencil** icon to enter edit mode. 2. Tap **Add Widget**. 3. Pick a widget type, then pick the entity to bind. 4. Tap **Done**. Changes saved automatically. 5. - **Settings > Theme.** Pick a preset or customize. [Themes guide](/docs/themes). - **Settings > Dashboards.** Add more dashboards, each with its own layout. - **Settings > Remote Access.** Get a `*.glasshome.cloud` URL via tunnel. [How](/docs/remote-access). ## What next - **Use it well:** [Widgets](/docs/widgets), [Editing](/docs/editing), [Layouts](/docs/layouts). - **Build your own widgets:** [Widget Development](/docs/widget-development). - **Something off:** [Troubleshooting](/docs/troubleshooting) or post in [Discord](https://discord.gg/FJYdeDmrzv) and I'll take a look. > **Mental model first?:** If terms like Hub, addon, scope, and tunnel are blurring together, the [Concepts](/docs/concepts) page is one screen. --- title: "Concepts" description: "The GlassHome mental model. Dash, Hub, addon, widget, tunnel. What each piece is, where it runs, and how they connect." slug: concepts canonical: https://glasshome.app/docs/concepts section: "Getting started" updated: 2026-06-09 --- # Concepts A handful of terms come up across the docs. Skim this once and the rest reads faster. ## The pieces ### Dash (glasshome-dash) The app you actually interact with. Runs on your hardware (Pi, NUC, NAS, HAOS). Talks directly to Home Assistant over WebSocket. Contains one or more dashboards (named screens you configure). ### Hub (this site) Cloud service at glasshome.app. Hosts the widget registry, your account, organizations, and tunnel provisioning. Optional for using Dash. ### Addon A packaging of Dash for Home Assistant OS or Supervised. Installed via the HA Add-on Store. Same app, easier install. ### Widget A tile on a dashboard bound to one or more HA entities. Built-in or installed from Hub. SolidJS component + Zod config. ## Where things run ``` Your network Internet ───────────────────────── ────────────────── Home Assistant ◄──WS──┐ glasshome.app │ (account, widget CDN, Dash ──────────────────┴───── browser tunnel provisioning) + optional cloudflared ───── tunnel ──────▲ ``` Dash does not need Hub to run. Hub is only required if you want to install community widgets, publish your own, or use the managed remote-access tunnel. ## Glossary ### Everyday terms - **Dashboard.** A named screen inside Dash. You can have multiple dashboards, each with its own layout and widget set. - **Breakpoint.** Layout slot keyed by screen width (`lg`, `md`, `sm`). Each dashboard stores one layout per breakpoint. See [Layouts](/docs/layouts). - **Tunnel.** A `cloudflared` process that exposes Dash to the internet via Cloudflare's edge. Provisioned by Hub. See [Remote Access](/docs/remote-access). ### Publishing terms - **Scope.** Publishing namespace. `@username` for personal, `@org-slug` for an [organization](/docs/organizations). Widget IDs look like `@scope/widget-name`. - **Manifest.** The `manifest.json` (or inline metadata in `defineWidget`) that describes a widget: name, icon, size limits, SDK version. - **configVersion.** Integer on a widget manifest. Bumped when the config shape changes in a breaking way. Drives [migrations](/docs/widget-migrations). - **Trust badge.** `Official` (verified, installs without prompt) or `Community` (user-published, asks for consent). See [Publishing](/docs/widget-publishing). ## Data flow 1. Browser loads Dash from your local server (e.g. `http://192.168.1.x:3123`). 2. Dash opens a WebSocket to Home Assistant for live entity state. 3. When you add a community widget, Dash fetches its bundle from Hub's CDN once, then caches it locally. 4. Dash config (layouts, themes, connections) is stored in a local SQLite database. No sync to Hub. > **What never leaves your network:** Entity state, your layouts, your HA URL, your devices. Dash talks to HA directly. Hub never sees any of it. ## Hub vs. Dash accounts You sign into Hub (this site) to publish widgets or provision a tunnel. Dash itself doesn't require an account. It authenticates to Home Assistant via HA's own OAuth. --- title: "Widget Security" description: "How GlassHome keeps third-party widgets from leaking your home data or controlling devices you did not approve. Plain language, layered, and honest about what is not yet covered." slug: widget-security canonical: https://glasshome.app/docs/widget-security section: "Dashboards" updated: 2026-06-12 --- # Widget Security Community widgets are code other people wrote, running on your dashboard. GlassHome is built so that even a malicious widget cannot quietly leak your home's data or control devices you did not approve. This page explains, plainly, how that protection is layered and where its edges are. ## The short version - **A widget cannot send your home's data to the internet.** Your browser only lets widgets talk to your Home Assistant and to GlassHome, nothing else. - **A widget can only control the devices you approve when you install it.** A weather widget cannot unlock your door, because it never gets the keys to do so. - **The widget you run is exactly the one I published.** It is checked byte-for-byte before it loads. ## Why not iframes? A common way to sandbox third-party code is to put each widget in its own iframe. GlassHome deliberately doesn't. Iframes are heavy: a dashboard full of them is slow, janky, and clunky to interact with, and that is exactly the experience GlassHome is built to avoid. Instead the rules about what a widget is *allowed to do* are enforced for it. That is what actually protects you: a malicious widget in a fast, native-feeling tile still cannot phone home or touch devices you did not approve. The layers below are what make that true. ## The layers Each layer stops a different thing. The first two are the real guarantees: your browser or GlassHome enforces them, and a widget cannot talk its way around them. The rest raise the bar further on top. ### Layer 1 — The network lock (stops data leaving) Your browser is told, up front, the only places a widget is allowed to send anything: your Home Assistant and GlassHome. A widget physically cannot reach any other server. So even a widget that reads everything on your dashboard has nowhere to send it. **Protects against:** a widget exfiltrating your presence, camera, lock, or sensor data to a stranger's server. ### Layer 2 — The locked-away keys (stops unapproved control) Your Home Assistant connection and login token live in a separate, sealed-off part of the app that widget code cannot reach. A widget never holds the keys to Home Assistant. When it wants to control something, it asks GlassHome, which checks the request against what you approved at install before doing anything. **Protects against:** a weather widget unlocking your door, a clock disarming your alarm, or any widget controlling a device you did not grant it, and against any widget reading your Home Assistant login. ### Layer 3 — Isolation and no hidden code (extra hardening) Each widget renders in its own sealed space, so it cannot reach into GlassHome or into other widgets, and its styling cannot bleed out. Widgets also cannot run code that was generated or downloaded on the fly. **Protects against:** one widget tampering with another or with the dashboard itself, and against a widget smuggling in hidden behavior after it is published. This layer makes misbehavior much harder, but unlike Layers 1 and 2 it is defense-in-depth rather than an absolute guarantee. ## The permission prompt When you install a widget that uses your devices, GlassHome shows you exactly what it is asking for, in plain language: > **"Climate" wants permission to: Control your thermostat** You approve that list, and only that list. The widget is held to it from then on. If a later update wants *more* than you approved, GlassHome asks you again before that update can run, never silently. And if a widget ever tries to do something outside what you approved, the attempt is **blocked** and you get a notification, so misbehavior is visible, not silent. ## What a widget can and cannot do | A widget can… | A widget cannot… | |---|---| | Show the entities you give it | Send your data to any other website | | Control the devices you approve at install | Control devices you did not approve | | Render inside its own tile | Read your Home Assistant login | | Use your theme and styling | Reach into GlassHome or other widgets' data | | | Run hidden, downloaded-on-the-fly code | ## Not yet covered I would rather name the edges than pretend they aren't there. - **A widget can still read what is already on your dashboard.** Layer 1 stops that data from *leaving*, but a malicious widget on the page can see the entity values shown there. The protection is that it has nowhere to send them, not that it cannot see them. - **Layer 3 is hardening, not a wall.** The isolation between widgets and the no-hidden-code rule raise the bar a lot, but a determined attacker who found a browser-level escape could in theory get around them. That is exactly why the guarantees that matter most, your data not leaving (Layer 1) and your Home Assistant keys staying sealed away (Layer 2), do not depend on Layer 3 at all. - **No human review of every widget yet.** Today the safety comes from the layers above plus the permission you grant at install, not from someone reading each widget's code before it is published. A review or signing step for the public catalog may come later. > **For the technically curious:** Layer 1 is a strict Content-Security-Policy egress allowlist. Layer 2 keeps the Home Assistant connection and access token in a Web Worker that widget code cannot reach; widgets send permission-checked requests to it (validated with the same capability definition the consent screen showed you) rather than talking to Home Assistant directly. Layer 3: each widget renders in a closed shadow root, intrinsics are frozen, and `eval`/`Function` are disabled by CSP. Separately, every published bundle is SHA-256 verified server-side before it is served. ## Reporting a security issue Found a way around any of this? I want to hear it. Email **contact@glasshome.app** with steps to reproduce, or reach me on the [GlassHome Discord](https://discord.gg/FJYdeDmrzv). I review every report and am glad to credit researchers who report responsibly. --- title: "Home Assistant Addon" description: "Install GlassHome as a Home Assistant addon. One-click setup for HAOS and Supervised users." slug: addon canonical: https://glasshome.app/docs/addon section: "Installation" updated: 2026-06-09 --- # Home Assistant Addon The easiest way to run GlassHome if you're on Home Assistant OS or Home Assistant Supervised. ## Prerequisites - Home Assistant OS or Home Assistant Supervised (both include the Add-on Store with Supervisor). - A 64-bit device: Raspberry Pi 4 or 5, or an x86-64 machine with SSE4.2. See [Supported CPUs](/docs/installation#supported-cpus). ## Installation 1. Add GlassHome to your addon store. Add Repository > **Button didn:** Open **Settings > Add-ons > Add-on Store**, click the **⋮ menu** (top-right) and choose **Repositories**. Paste this URL and click **Add**: > > ``` > https://github.com/glasshome/homeassistant-addon > ``` 2. Find GlassHome in the Add-on Store and click Install. 3. Start the addon. Open `http://:3123` in your browser. > **No sidebar entry:** Dash does not appear as an item in the HA sidebar (ingress is not supported). Open it directly at `http://:3123`, where `` is the IP address of your Home Assistant machine. ## Updating Check **Settings > Add-ons** for available updates. You'll see a notification when a new version is available. ## Edge Channel I publish early builds here for testing before they ship in the stable release. The same repository includes an **Edge** version of GlassHome. Enable **Advanced Mode** in your HA profile to see it in the Add-on Store. Edge runs on port **3124**, so it can run side by side with the stable addon. > **Edge is for testing:** Edge builds may have rough edges. Use the stable version for your daily driver. ## Troubleshooting - **Addon not visible after adding the repository.** Refresh the Add-on Store page (the ⋮ menu has a "Check for updates" option). It can take a moment for the new addon to appear. - **Addon fails to start: port conflict.** Port 3123 may already be in use on your HA host. Check for other services listening on that port and stop them before starting GlassHome. - **Addon fails to start: unsupported architecture.** GlassHome requires a 64-bit CPU (Pi 4/5, x86-64 with SSE4.2). 32-bit installs and Pi 3 are not supported. See [Supported CPUs](/docs/installation#supported-cpus). ## Related docs - [Connecting to Home Assistant](/docs/connecting): pair Dash with HA after install - [Docker installation](/docs/docker): alternative install for non-HAOS setups - [Quickstart](/docs/quickstart): first dashboard in five minutes - [Troubleshooting](/docs/troubleshooting): common install issues --- title: "Bug Reports" description: "How to report bugs in GlassHome. What to include, where to find logs, and how to help get it fixed fast." slug: bug-reports canonical: https://glasshome.app/docs/bug-reports section: "Troubleshooting" updated: 2026-06-09 --- # Bug Reports Something not working right? Your reports genuinely help shape GlassHome into something better. ## Where to Report Head to the [GlassHome Discord](https://discord.gg/FJYdeDmrzv) and post in the **#bug-reports** channel. ## What to Include The more context you give, the faster I can track it down. Try to include as many of these as you can: - **What happened.** Describe what you did and what went wrong. "I tapped the light widget and nothing happened" is better than "light widget broken". - **What you expected.** What should have happened instead. - **Steps to reproduce.** Can you make it happen again? Step-by-step instructions help a lot, even if they seem obvious. - **Screenshot or screen recording.** A picture is worth a thousand words. A short video of the issue is even better. - **GlassHome version.** Found in **Settings > About** at the bottom of the settings page. - **Device and browser.** What are you running GlassHome on? Phone, tablet, desktop? Which browser? This matters for layout and touch issues. - **How you installed GlassHome.** HA addon, Docker, or something else? Include the architecture (amd64, aarch64) if you know it. ## Getting Logs Logs often reveal what went wrong behind the scenes. Here's how to get them. ### HA Addon Logs Go to **Settings > Add-ons > GlassHome Dashboard > Log** in Home Assistant. Copy the output. ### Docker Logs ```bash docker logs glasshome ``` ### Browser Console Open your browser's developer tools (F12 or Ctrl+Shift+I), go to the **Console** tab, and copy any red errors. This is especially useful for UI bugs and widget issues. ## Template Feel free to copy-paste this into Discord and fill it in: ``` **What happened:** [describe the bug] **Steps to reproduce:** 1. [first step] 2. [second step] 3. [what goes wrong] **Expected behavior:** [what should have happened] **Version:** [e.g. 0.8.2] **Device:** [e.g. iPad, Chrome on Windows, Pixel 8] **Install method:** [HA addon / Docker] **Logs / screenshots:** [paste logs or attach screenshots] ``` > **Not sure if it:** Post it anyway. If it turns out to be a question or a feature request, I'll point you in the right direction. --- title: "Changelog" description: "Release history for GlassHome Dashboard and Hub. Authoritative source is the GitHub Releases page." slug: changelog canonical: https://glasshome.app/docs/changelog section: "Reference" updated: 2026-06-09 --- # Changelog Authoritative releases live on [GitHub Releases](https://github.com/glasshome/dash/releases). This page summarizes notable user-facing changes. ## 0.9.x (current line) See [GitHub Releases](https://github.com/glasshome/dash/releases) for individual entries. ## How versioning works - **Dashboard** uses semver. Patch = bug fixes; minor = features; major = breaking config / SDK changes. - **Widget SDK** ships independently on npm (`@glasshome/widget-sdk`). Each widget declares an `sdkVersion` range in its manifest. See [Widget SDK](/docs/widget-sdk). - **Widget config version** (`configVersion`) is a per-widget integer you bump when your widget's config shape has breaking changes. See [Config Migrations](/docs/widget-migrations). - **CLI minimum version** is enforced by Hub per-deploy. The current floor is served from `/api/widgets/cli-version`. See [Widget CLI](/docs/widget-cli#version-requirements). - **Hub API** is versioned at the URL (`/api/v1/...`). Older versions remain until announced sunset. See [API Reference](/docs/api-reference#api-versioning-and-sunset). ## Subscribing - RSS feed: [/docs/rss.xml](/docs/rss.xml) - GitHub Releases watch. - Announcements in [Discord](https://discord.gg/FJYdeDmrzv) `#announcements`. --- title: "Connecting to Home Assistant" description: "Pair GlassHome with your Home Assistant instance via OAuth. Setup wizard, demo mode, URL format pitfalls, and reconnection." slug: connecting canonical: https://glasshome.app/docs/connecting section: "Getting started" updated: 2026-06-09 --- # Connecting to Home Assistant After [installing](/docs/installation), open `http://:3123`. The setup wizard runs on first launch. ## Connection methods ### OAuth (recommended) Paste your HA URL. Authorize in the HA popup. The wizard returns automatically. No tokens to copy or rotate. ### Demo Mode Explore with simulated entities, no HA needed. Pick it from the welcome screen. Ready for the real thing? Choose Exit demo in Settings, then connect your HA from the welcome screen. ## Entering the URL The URL must include the protocol and the port HA is actually listening on. | Setup | URL example | |------------------------------------------|----------------------------------------------| | HA on the same LAN, plain HTTP | `http://homeassistant.local:8123` | | HA on a fixed IP | `http://192.168.1.100:8123` | | HA behind HTTPS (reverse proxy / Nabu) | `https://ha.example.com` | > **The URL must match what HA serves:** If HA is behind HTTPS, use `https://`. If HA is plain HTTP, use `http://`. A mismatch fails silently or returns a confusing CORS error. ## What the wizard does 1. Verifies the URL responds. 2. Redirects to HA for OAuth consent. 3. HA returns a refresh token to Dash. 4. Token is stored in Dash's local SQLite (never sent to Hub). ## Reconnecting To swap to a different HA instance, or repair a broken connection: **Settings > Connections > Reconnect**. Your dashboards and layouts are stored locally and remain after reconnecting; only the HA auth token is replaced. ## Privacy Dash talks to HA directly over WebSocket. Entity state, your HA URL, and the auth token never leave your network. Hub is not in this path. ## Troubleshooting | Symptom | Likely cause | Fix | |---------|-------------|-----| | Wizard rejects the URL | Missing protocol or wrong port. | Try the URL in a browser tab first. Add `http://` or `https://` and confirm the port. | | Stuck on "Authorizing..." | Popup blocked, or HA is unreachable from your browser (not just from the Dash host). | Allow popups for the Dash URL, then confirm your browser can reach HA directly. | | Mixed-content / CORS error in console | HTTPS Dash URL + plain HTTP HA URL (or vice versa). | Options: serve HA over HTTPS (Nabu Casa or a reverse proxy), access Dash over plain HTTP on your local network, or use a VPN so both are on the same network. | | Connection drops after restart | HA was rebooted while the WebSocket was open. | Refresh the page. Dash reconnects automatically. | | Connected but entities not loading | Token user has no entity permissions in HA. | Re-authorize with a fully-permitted HA user. | Still stuck? See [Troubleshooting](/docs/troubleshooting) or post in [Discord](https://discord.gg/FJYdeDmrzv) and I'll take a look. ## Related docs - [Home Assistant Addon](/docs/addon) or [Docker](/docs/docker): install Dash first - [Quickstart](/docs/quickstart): what to do once connected - [Remote Access](/docs/remote-access): reach your dashboards from outside your network --- title: "Layouts" description: "Responsive grid breakpoints and managing multiple dashboards. Layouts are per breakpoint and per dashboard." slug: layouts canonical: https://glasshome.app/docs/layouts section: "Dashboards" updated: 2026-06-09 --- # Layouts Each dashboard keeps its own independent layout per breakpoint. Rearranging on your phone does not change the desktop layout. > **Layouts are per breakpoint:** Dash stores a separate widget arrangement for each screen-width breakpoint. Changes you make on a phone (small breakpoint) leave the tablet and desktop layouts untouched. ## Responsive Breakpoints The grid adapts columns to screen width. | Breakpoint | Width | Columns | |---|---|---| | `lg` | ≥1024px | 12 | | `md` | ≥768px | 8 | | `sm` | <768px | 4 | ## Editing All editing (moving, resizing, adding, removing widgets) happens in edit mode. See [Editing](/docs/editing) for the full gesture reference and step-by-step. ## Multiple Dashboards Create dashboards in **Settings > Dashboards**. Each has its own widget set and independent layout per breakpoint. Switch between dashboards via the dock at the bottom of the screen. Manage dashboards (rename, delete, reorder) in **Settings > Dashboards**. --- title: "Pro" description: "What GlassHome Pro unlocks, how the free tier compares, how to buy, and how to manage your one-time Pro license." slug: pro canonical: https://glasshome.app/docs/pro section: "Hub" updated: 2026-06-09 --- # Pro GlassHome is free and stays free. Pro is a one-time purchase that unlocks two extras on top of the free dashboard. Pay once, it is yours, there is no subscription. ## Free vs Pro The full local dashboard is free forever and needs no account or payment. | | Free | Pro | |---|------|-----| | Connect to Home Assistant | Yes | Yes | | All official `@glasshome` widgets | Yes | Yes | | All 7 theme presets | Yes | Yes | | Account required | No | Yes (to hold the license) | | All v1 updates | Yes | Yes | | Community widgets from the Hub | | Yes | | Theming (theme editor, custom backgrounds, theme upload) | | Yes | Everything in Free stays in Free. Pro adds **community widgets** (install widgets published to the Hub registry, not just the official set) and **theming** (the in-app theme editor, custom backgrounds, and theme upload). Custom themes already saved on your Dash stay usable without Pro; only creating and editing them is gated, so nothing you built breaks if Pro lapses. ## Pricing Pro is a one-time purchase, not a subscription. The standing price is **$39.99**. The current **Early Bird** offer is **$19.99** and runs until end of June 2026. The current offer and any countdown are shown on the [pricing page](/pricing). Whatever price you buy at is what you pay, with nothing recurring. All v1 updates are included. > **One-time, not a subscription:** Buying Pro grants a permanent license on your account. There is no renewal and no recurring charge. ## How to buy 1. Sign in (or create an account) on the Hub. An account is what holds the license. 2. Go to the [pricing page](/pricing) and start checkout. Payment is handled by Polar. 3. After checkout completes, the Pro entitlement lands on your account automatically and your license card appears in your [account dashboard](/dashboard). You do not need an account to *use* GlassHome locally; you only need one to hold a Pro license or to publish widgets. ## Managing your license - **License card.** Your Pro license, including your sequential member number, is shown on your [account dashboard](/dashboard). The member number is assigned once and never changes. - **Sign in anywhere.** Pro is tied to your account, not a device. Link and unlink Dash instances whenever you like; signing in on another device or a fresh install restores Pro. - **Refunds.** Checkout and billing run through Polar. If you need a refund, email [contact@glasshome.app](mailto:contact@glasshome.app) and I will sort it out. > **Questions about pricing:** The [FAQ](/docs/faq) and the [pricing page](/pricing) cover the common questions. If something is still unclear, ask, I would rather answer than have you guess. --- title: "Widget SDK" description: "The GlassHome widget SDK API. defineWidget, Widget components, hooks, entity bindings, and theming." slug: widget-sdk canonical: https://glasshome.app/docs/widget-sdk section: "Widget Development" updated: 2026-06-09 --- # Widget SDK `@glasshome/widget-sdk` (latest: **1.1.1**, published on npm) provides the `defineWidget` API, composable UI components, reactive hooks, the Home Assistant data layer, and theming utilities for SolidJS-based widgets. ## defineWidget The entry point for every widget. Combines a manifest (metadata), an optional Zod config schema, an optional `migrate` function, and a SolidJS component. ```ts function defineWidget>( definition: WidgetDefinition ): WidgetDefinition ``` `WidgetDefinition` shape: ```ts interface WidgetDefinition> { manifest: WidgetManifest; configSchema?: ZodType; migrate?: (config: Record, fromConfigVersion: number) => Record; component: (props: { config: C }) => any; } ``` When you provide `configSchema`, `defineWidget` auto-populates `manifest.schema` (JSON Schema) and `manifest.defaultConfig` from it. TypeScript types are derived via `z.infer`. The dashboard renders the edit form from the schema automatically. ## Manifest fields The `manifest` object inside `WidgetDefinition` is authoritative. The CLI reads `src//manifest.json` for build and publish metadata; `bun widget upgrade` writes `sdkVersion` back to that file. Both the inline `manifest` (in `defineWidget`) and `manifest.json` are used: the inline manifest is what the widget runtime loads; `manifest.json` is what the CLI reads. Keep them in sync (the build step warns on schema drift). | Field | Type | Required | Description | |---|---|---|---| | `name` | `string` | yes | Display name and identifier | | `description` | `string` | no | Short description shown in the widget picker | | `icon` | `string` | no | Iconify icon, e.g. `"mdi:lightbulb"` | | `minSize` | `{w, h}` | yes | Minimum grid size | | `maxSize` | `{w, h}` | yes | Maximum grid size | | `defaultSize` | `{w, h}` | no | Initial size when added to a dashboard | | `sdkVersion` | `string` | yes | Required SDK version range, e.g. `"^1.0.0"` | | `capabilities` | `CapabilityGrant[]` | yes (1.x) | Home Assistant domains the widget reads or controls; approved by the user, enforced by the host. Empty array for widgets that touch no HA data. See [Capabilities & Permissions](/docs/widget-capabilities). | | `configVersion` | `number` | no | Bump when config shape changes; used with `migrate` | | `schema` | `object` | no | Auto-populated from `configSchema`. Do not set manually. | | `defaultConfig` | `object` | no | Auto-populated from Zod `.default()` calls. | | `cssUrl` | `string` | no | Set by the build when the widget emits a stylesheet. Do not set manually. | ## widgetFields Pre-built Zod field helpers. Each attaches metadata that the dashboard uses to render the right form control. | Helper | Renders as | |---|---| | `widgetFields.title()` | Text input labelled "Title", hint "Optional display name override" | | `widgetFields.entityIds(domain)` | Multi-select entity picker filtered by domain | | `widgetFields.singleEntity(domain)` | Single-select entity picker filtered by domain | | `widgetFields.areaId()` | Area picker dropdown | For other types, use Zod directly. `z.boolean()` renders a switch, `z.enum([...])` a dropdown, `z.number()` a number input. Add `.meta({ title: "Label" })` to set the display label. ## Complete widget example A working example that uses the real SDK API: imports, config schema, `defineWidget`, entity binding via `useWidgetEntityGroup`, state reaction, and tap gesture. ```tsx // src/my-lights/index.tsx import { defineWidget, Widget, useWidgetEntityGroup, useWidgetGestures, useWidgetDialog, WidgetDialog, widgetFields, } from "@glasshome/widget-sdk"; import { z } from "zod"; const configSchema = z.object({ title: widgetFields.title(), entityIds: widgetFields.entityIds("light"), }); type Config = z.infer; function MyLightsWidget(props: { config: Config }) { const { openDialog, dialogProps } = useWidgetDialog(); const { aggregatedData, emptyState, hasEntities } = useWidgetEntityGroup({ entities: () => [], // host injects real entities via the sync layer at runtime aggregationMode: () => "light", emptyStateConfig: { icon: 💡, title: "No lights", message: "Configure this widget to add lights", }, }); const gestures = useWidgetGestures( () => ({ tap: () => { // toggle or open dialog on tap openDialog(); }, hold: { action: openDialog }, }), ); const lightData = () => aggregatedData(); const isOn = () => lightData()?.isOn ?? false; return ( <> {props.config.title || "Lights"} {isOn() ? "On" : "Off"} {lightData() ? `${lightData()!.onCount}/${lightData()!.totalCount}` : ""} ); } export default defineWidget({ manifest: { name: "My Lights", description: "Toggle a group of lights", icon: "mdi:lightbulb-group", minSize: { w: 1, h: 1 }, maxSize: { w: 4, h: 4 }, sdkVersion: "^1.0.0", capabilities: [{ domain: "light", access: "control" }], }, configSchema, component: MyLightsWidget, }); ``` > **Host injects entities:** The `entities` accessor inside `useWidgetEntityGroup` receives `EntityView[]` from the host sync layer at runtime. In your source code, pass an accessor that reads from whatever entity source the host provides. The scaffold template shows the exact pattern for your project. ## Widget component `` is the main container. It provides context, gesture handling, theming, and base styling. ### Props ```ts interface WidgetProps { variant?: string | WidgetVariantConfig; tone?: Tone; // semantic color: resolves to --widget-color color?: string; // CSS color override for --widget-color (overrides tone) colorTo?: string; // second gradient stop (--widget-color-to) gradient?: string; // full CSS gradient (overrides auto-shell) loading?: boolean; class?: string; isEditMode?: boolean; onDelete?: () => void; emptyState?: { icon?: JSX.Element; title?: string; message?: string }; gestures?: GestureHandlers; // return value of useWidgetGestures(...) children?: JSX.Element; } ``` Pass the return value of `useWidgetGestures(...)` to `gestures`, not a plain object. ### Slot subcomponents | Slot | Key props | Purpose | |---|---|---| | `Widget.Icon` | `icon: JSX.Element`, `color?`, `dimmed?`, `entityCount?` | Entity icon with adaptive color | | `Widget.Title` | `children` | Primary label | | `Widget.Status` | `children` | State text (On, Off, etc.) | | `Widget.Value` | `children` | Numeric or formatted value | | `Widget.Content` | `children`, `class?` | Freeform content area | | `Widget.SliderFill` | (see SDK types) | Background fill for slider widgets | ### Component props Every widget component receives one prop: `{ config: C }` where `C` is the type you pass to `defineWidget`. The host parses, migrates, and validates the saved config before passing it, so `config` always matches your current schema type. ## Hooks ### `useWidgetContext()` Returns `ReactiveWidgetContext`. Must be called inside the `` render tree. ```ts interface ReactiveWidgetContext { isEditMode: () => boolean; updateConfig: (config: Record) => void; dimensions: () => { width: number; height: number }; // CSS px, (0,0) before first layout registerDialogOpener?: (open: ((tab?: string) => void) | null) => void; } ``` ### `useWidgetEntityGroup(options)` Aggregates one or many entities with built-in aggregation presets and empty-state support. ```ts function useWidgetEntityGroup( options: UseWidgetEntityGroupOptions ): UseWidgetEntityGroupResult ``` **Options:** | Field | Type | Description | |---|---|---| | `entities` | `Accessor` | Reactive entities array (required) | | `emptyStateConfig` | `WidgetEmptyStateConfig` | Shown when `hasEntities` is false (required) | | `aggregationMode` | `Accessor` | `"light"`, `"sensor"`, `"switch"`, `"binary-sensor"`, `"none"` | | `sensorGroupType` | `Accessor` | For `"sensor"` preset: `"min"`, `"max"`, `"mean"`, `"median"`, `"sum"`, `"last"`, etc. | | `calculateGroupData` | `(entities: EntityView[]) => TData` | Custom aggregation function (alternative to `aggregationMode`) | | `allEntitiesMode` | `boolean` | All entities must be on for group to be "on" (light/switch presets) | | `minEntities` | `number` | Minimum entities required; default `1` | **Return value:** | Field | Type | Description | |---|---|---| | `entities` | `Accessor` | All entities | | `groupData` | `Accessor` | Result of `calculateGroupData`, or `null` | | `aggregatedData` | `Accessor` | Result of preset aggregation | | `emptyState` | `Accessor` | Defined when entities are missing | | `hasEntities` | `Accessor` | Whether minimum entities are present | | `count` | `Accessor` | Entity count | `LightGroupResult` includes: `isOn`, `isUnavailable`, `brightness` (0-255), `brightnessPercent` (0-100), `color`, `onCount`, `totalCount`, `description`. ### `useWidgetDialog(defaultTab?)` Controls the widget settings dialog. `defaultTab` defaults to `"controls"`. ```ts interface WidgetDialogReturn { showDialog: () => boolean; setShowDialog: (open: boolean) => void; openDialog: (tab?: string) => void; closeDialog: () => void; activeTab: () => string; setActiveTab: (tab: string) => void; dialogProps: { open: boolean; onOpenChange: (open: boolean) => void; activeTab: string; onActiveTabChange: (tab: string) => void; }; } ``` Spread `dialogProps` onto `` to wire state automatically. ### `useWidgetGestures(config, orientation?)` Tap, hold, and slide gestures on any pointer type. ```ts function useWidgetGestures( config: () => GestureConfig, orientation?: () => "horizontal" | "vertical" | "square" ): GestureHandlers ``` Both arguments are SolidJS accessors. The orientation arg is optional (defaults to horizontal). ```ts interface GestureConfig { tap?: () => void; hold?: { action: () => void; delay?: number }; // delay in ms, default 300 slide?: { value: number; onChange: (value: number) => void; min?: number; max?: number; orientation?: "auto" | "horizontal" | "vertical"; activationDelay?: number; }; } ``` `GestureHandlers` (returned value) carries `onPointerDown`, `onPointerMove`, `onPointerUp`, `onPointerCancel`, `onPointerEnter`, `bindElement`, `touchAction`, and `dispose`. Pass the whole object to ``. Call `dispose()` in `onCleanup`. ### `useReducedMotion()` Returns `() => boolean`. Reactive `prefers-reduced-motion` accessor. Returns `false` when `matchMedia` is unavailable (SSR-safe). ### `useIntersectionPause(elAccessor)` Returns `() => boolean`: `true` while the given element is off-screen. Pass an accessor returning the element to observe. Returns `false` when `IntersectionObserver` is unavailable (SSR-safe). ## Home Assistant data & services All Home Assistant access goes through hooks re-exported from the SDK. Import them from `@glasshome/widget-sdk`, never from `@glasshome/sync-layer` directly (a direct import fails the build, and would bundle a second, disconnected store). Every read hook is a SolidJS accessor backed by the host's live store, so your UI stays reactive without managing any WebSocket state. Reads and service calls are checked against the [capabilities](/docs/widget-capabilities) your manifest declares. ### Reading entities | Hook | Returns | Purpose | |---|---|---| | `useEntity(id)` | `Accessor` | One entity by id (`id` may be a string or accessor) | | `useEntities(ids)` | `Accessor` | Many entities; pass an accessor returning the id array | | `useArea(id)` | `Accessor` | An area and its entities | | `useEntityHistory(id)` | `Accessor` | Recent state history for an entity | | `useEntityStatistics(id, options)` | `Resource` | Long-term statistics; the resource exposes `.loading` and `.error` | | `useForecast(id)` | `Accessor` | Weather forecast for a weather entity | | `useCamera(id)` | `{ stream, refresh }` | Reactive camera stream data | | `useStore(selector)` | `Accessor` | Escape hatch for direct store access when no specific hook fits | ```tsx import { useEntity, useEntities } from "@glasshome/widget-sdk"; function Thermostat(props: { config: Config }) { const climate = useEntity(() => props.config.entityId); const temp = () => climate()?.attributes.current_temperature; return {temp() ?? "—"}; } ``` ### Connection & locale | Hook | Returns | |---|---| | `useConnection()` | `{ status, isConnected }` accessors | | `useHassConfig()` | `Accessor` | | `useUnitSystem()` | `Accessor` | | `useTemperatureUnit()` | `Accessor` (e.g. `"°C"`) | | `useLocale()` | `Accessor` (BCP 47) | | `useCurrency()` | `Accessor` (ISO 4217) | ### Calling services `useService()` returns a capability-routed service caller plus the common shortcuts. The shortcuts take an entity id (or array) and optional service data: ```tsx import { useService } from "@glasshome/widget-sdk"; function LightToggle(props: { config: Config }) { const { toggle, turnOn, callService } = useService(); return ( ({ tap: () => toggle(props.config.entityId) }))}> {/* turnOn("light.x", { brightness_pct: 80 }) */} {/* callService("light", "turn_on", { brightness_pct: 80 }, { entity_id: "light.x" }) */} ); } ``` | Hook | Returns | |---|---| | `useService()` | `{ callService, turnOn, turnOff, toggle }` | | `useTurnOn()` / `useTurnOff()` / `useToggle()` | The single shortcut function | `callService(domain, service, data?, target?)` is the general form; it resolves to `Promise`. The host validates the call against your granted capabilities before forwarding it to Home Assistant. ## Errors and empty states - **Config schema parse failure.** If the stored config fails Zod validation after migration, the dashboard falls back to `configSchema.parse({})` (all-defaults). If that also fails, it passes `{}` to the component. No error is shown to the user; your component should handle missing/default config gracefully. - **Missing entities.** Pass an `emptyStateConfig` to `useWidgetEntityGroup`. When `hasEntities()` is false, `emptyState()` returns the config object; pass it to `` to render the built-in empty state UI. - **Component throw.** If your component throws during render, the dashboard catches it with an `ErrorBoundary`. After one crash it shows a retryable error card with a Retry button. After three crashes (configurable in the Dash host) it shows a permanent "disabled" card until the page is refreshed. ## Theming Widgets inherit the dashboard theme automatically and render in an isolated shadow root with their own CSS bundle. The essentials: - Use the `tone` prop on `` for semantic color (`tone="accent"`), or `color`/`colorTo`/`gradient` for a custom shell. - `isDark()` returns the current theme as a boolean for logic; `dark:` Tailwind variants work in markup. - The SDK also exports `injectTokens`, `Tone`, and `ToneSchema`. The full styling model (Tailwind, your own CSS files, theme variables, container queries, and animation) is covered in **[Styling & Animation](/docs/widget-styling)**. ## Entity utilities | Function | Description | |---|---| | `isEntityActive(entity)` | `true` for active states | | `getEntityAttribute(entity, key)` | Read a specific attribute | | `countActiveEntities(entities)` | Count of currently active entities | | `calculateLightGroup(entities, allEntitiesMode?)` | Aggregate brightness/color/state across a light group | | `calculateSensorGroup(entities, groupType?, ignoreNonNumeric?)` | Aggregate numeric sensors | ## Build tooling The scaffolded `vite.config.ts` wires this up automatically. Two entrypoints for custom setups: ### `@glasshome/widget-sdk/vite` | Export | Purpose | |---|---| | `glasshomeWidget(options?)` | Single-widget plugin: dev preview + library build | | `glasshomeWidgets(options?)` | Multi-widget project plugin: per-widget builds + `registry.json` | | `buildWidgets(options?)` | Programmatic build of all widgets (used by `bun widget build`) | | `discoverWidgets(srcDir)` | Scan `src/` for subdirs containing `index.tsx` + `manifest.json` | | `generateRegistry(srcDir, outDir)` | Write a `registry.json` from discovered manifests | | `isWidgetExternal(id)` | `true` for host-provided packages (not bundled) | ### `@glasshome/widget-sdk/schemas` Zod schemas for validating manifests and publish payloads. Used by the CLI and Hub. | Export | Purpose | |---|---| | `WidgetManifestSchema` | Validate a complete widget manifest | | `GridSizeSchema` | Validate `{ w, h }` | | `parseGridSize` / `serializeGridSize` | Parse from / serialize to the stored JSON form | | `formatSchemaError` | Turn a Zod error into a readable message | | `PublishRequestSchema` / `PublishConfirmSchema` / `PublishBodySchema` | Publish-flow request bodies | --- title: "Capabilities & Permissions" description: "How a widget declares the Home Assistant access it needs. The capability grammar, access levels, narrowing to entities and services, what the user approves, and how the host enforces it." slug: widget-capabilities canonical: https://glasshome.app/docs/widget-capabilities section: "Widget Development" updated: 2026-06-13 --- # Capabilities & Permissions A widget runs sandboxed: it renders in an isolated shadow root, holds no Home Assistant connection or token, and cannot reach the 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 when they install the widget, and the host enforces it on every call. Access is **deny-by-default**: anything you don't declare is blocked at runtime. So the first thing to get right when building a widget is its capability list. > **Info:** This page is the developer's view: how to declare and scope access. For the homeowner-facing explanation of the sandbox and why it's safe, see Widget Security. ## How access works All Home Assistant interaction goes through the SDK's [data and service hooks](/docs/widget-sdk#home-assistant-data--services). There is no other path: importing `@glasshome/sync-layer` directly fails the build, and `fetch`/WebSocket to Home Assistant is blocked. Each call carries your widget's declared capabilities, and the host checks the call against them before touching Home Assistant. - **Reads** (entity state, history, forecasts) require a `read` grant for the domain. - **Service calls** (turning a light on, setting a thermostat) require a `control` grant for the domain. - A call that no grant authorizes is rejected, and the user is notified. ## Declaring capabilities 1. 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 makes the widget work. A widening update has to be re-approved by the user, and an over-broad request makes people hesitate to install. 2. Each grant is `{ domain, access }`, plus optional narrowing: ```jsonc // manifest.json "capabilities": [ { "domain": "light", "access": "control" }, { "domain": "sensor", "access": "read" } ] ``` The same array goes in the inline `manifest` you pass to `defineWidget`. A widget that touches no Home Assistant data (a clock, say) declares an empty array: ```jsonc "capabilities": [] ``` Targeting SDK 1.x **without** a `capabilities` array is rejected at publish. A manifest may declare at most 32 grants. 3. Read and control through the SDK hooks. Reads need a matching `read` (or `control`) grant; service calls need `control`: ```tsx 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 the ability to read the same domain, so you don't need both for one domain. ## Narrowing a grant By default a grant covers a whole domain. Tighten it to specific entities or services when the widget only needs part of one. ### To specific entities `entities` is a list of entity-id patterns. The domain part stays literal; `*` globs the object id only, so a pattern can never widen a grant past its domain. ```jsonc { "domain": "light", "access": "control", "entities": ["light.living_*", "light.kitchen"] } ``` A grant scoped to specific entities cannot reach **domain-wide** services (a service call with no entity target). Leave `entities` off if the widget legitimately needs the whole domain. ### To specific services `services` restricts a `control` grant to named services. A call to any service outside the list is rejected. ```jsonc { "domain": "media_player", "access": "control", "services": ["media_play", "media_pause"] } ``` Both `entities` and `services`, when present, must list at least one entry. ## What the user approves The host renders each grant as a plain-language sentence on the install consent screen, generated from the exact same object the host enforces, so the prompt can never drift from what's actually 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 | The user approves that list and only that list. ## 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. There is no way for widget code to opt out of the check. - **Widening re-prompts.** If an update asks for more than the user approved (a new domain, broader access, removed narrowing), the host asks the user to approve again before the update can run. Narrowing or unchanged grants install silently. - **Verified on publish.** The Hub validates your capability grammar and the bundle hash before a version goes live. ## See also ### [Data & service hooks](/docs/widget-sdk#home-assistant-data--services) The SDK hooks every capability gates: useEntity, useService, and the rest. ### [Widget Security](/docs/widget-security) The homeowner-facing view of the sandbox and what it guarantees. --- title: "Docker Deployment" description: "Deploy GlassHome with Docker Compose or standalone container. Multi-arch image, amd64 and aarch64 auto-selected." slug: docker canonical: https://glasshome.app/docs/docker section: "Installation" updated: 2026-06-09 --- # Docker Run GlassHome with Docker Compose or as a standalone container. The image is multi-arch: Docker picks the right build for your machine automatically (amd64 or aarch64). > **VM or older CPU?:** If Dash crashes immediately with `signal 4 / illegal instruction`, your CPU or VM profile is missing SSE4.2. See [Supported CPUs](/docs/installation#supported-cpus) for the fix. ## Docker Compose ```yaml services: glasshome: image: ghcr.io/glasshome/dash:latest container_name: glasshome restart: unless-stopped ports: - "3123:3123" volumes: - glasshome_data:/data environment: - PORT=3123 - DATA_DIR=/data volumes: glasshome_data: driver: local ``` `PORT` and `DATA_DIR` default to the values shown. You only need to set them if you want to change the internal port or data path. If you change the internal port, update the left side of the `ports` mapping to match. ## Direct Container ```bash docker run -d \ --name glasshome \ -p 3123:3123 \ -v glasshome_data:/data \ --restart unless-stopped \ ghcr.io/glasshome/dash:latest ``` > **Don:** The `glasshome_data:/data` volume stores your SQLite database, dashboards, settings, and connections. All your data lives here. ## Container Image Published to `ghcr.io/glasshome/dash:latest`. Runs on amd64 (PCs, Intel/AMD servers) and aarch64 (Raspberry Pi, Apple Silicon). ## Ports Default `3123`. Override with `-p YOUR_PORT:3123`. ## Updating **Docker Compose:** ```bash docker compose pull && docker compose up -d ``` **Standalone container** (no Compose): ```bash docker pull ghcr.io/glasshome/dash:latest docker rm -f glasshome docker run -d \ --name glasshome \ -p 3123:3123 \ -v glasshome_data:/data \ --restart unless-stopped \ ghcr.io/glasshome/dash:latest ``` Data in the `glasshome_data` volume is not touched by either method. ## Beta builds I publish early builds for testing before they ship in a stable release (the same builds the addon's Edge channel tracks). Use the `beta` tag instead of `latest`, or pin a specific version from the [GHCR tags page](https://github.com/glasshome/dash/pkgs/container/dash). ``` ghcr.io/glasshome/dash:beta ``` > **Beta is for testing:** Beta builds may have rough edges. Use the stable `latest` tag for your daily driver. To go back to stable, switch the tag back to `latest` and pull again. ## Related docs - [Connecting to Home Assistant](/docs/connecting): pair Dash with HA after install - [Home Assistant Addon](/docs/addon): one-click install for HAOS users - [Quickstart](/docs/quickstart): first dashboard in five minutes - [Troubleshooting](/docs/troubleshooting): common install issues --- title: "Editing Your Dashboard" description: "Enter edit mode, add and remove widgets, move and resize tiles. Full gesture reference for live and edit mode." slug: editing canonical: https://glasshome.app/docs/editing section: "Dashboards" updated: 2026-06-09 --- # Editing Your Dashboard How to enter edit mode, arrange widgets, and what every gesture does in both live and edit mode. ### Tap the pencil The pencil icon in the header opens edit mode. A faint grid overlay appears. ### Rearrange Move, resize, add, remove. Every action saves automatically. ### Tap Done Returns to the live dashboard. No Save or Discard buttons; changes are already persisted. ## Gestures The same input produces different effects depending on whether you are in live mode or edit mode. | Gesture (desktop) | Gesture (touch) | Live mode | Edit mode | |---|---|---|---| | Click | Tap | Primary action (toggle, run scene) | Select widget | | Click and hold 500 ms | Long-press 500 ms | Open detail dialog (device controls) | (n/a) | | Click and hold 300 ms | Long-press 300 ms | (n/a) | Pick up tile (start drag) | | Click and drag inside tile | (detail dialog) | Adjust primary value (brightness, cover) | (n/a) | | Drag | Drag (after pickup) | (n/a) | Move tile; surrounding widgets reflow | | Drag corner grip | Drag corner grip | (n/a) | Resize tile | Pickup in edit mode is intentionally faster (300 ms vs 500 ms) because accidental drags are easy to undo by dropping in place, whereas an accidental detail dialog opening in live mode is more disruptive. > **Why mobile skips in-tile sliders:** In-tile sliders fought with page scroll on touch. No heuristic reliably told a deliberate slide from a scroll. Moving fine control into the detail dialog kept gestures predictable and page scroll snappy. On desktop the mouse uses a wheel for scrolling, so the conflict does not exist and in-tile drag stays. ## Adding In edit mode, tap **Add Widget** in the header. Pick a widget from the picker and it lands in the first free spot at the current breakpoint. Drag it where you want, then tap **Done**. For widget types, community widgets, and building your own, see [Widgets](/docs/widgets). ## Moving ### Desktop Click and hold for 300 ms to pick up, then drag. Drop on any free space; surrounding widgets reflow. ### Mobile Long-press for 300 ms until you feel a haptic bump, then drag. Quick swipes scroll the page; page scrolling also works in the gaps between widgets. > **Autoscroll while dragging:** Drag a picked widget toward the top or bottom edge of the screen and the page scrolls automatically. You can place a widget anywhere on a long dashboard without dropping it. ## Resizing Drag the small grip at the bottom-right corner of any widget. The touch hit area extends beyond the visible grip so even 1x1 tiles are resizable with a finger. Each widget declares its own min and max size; the grip stops when you reach a limit. ## Removing In edit mode each widget shows an **X** in the top-right corner. Tap it and the widget is gone, with no confirmation prompt. The widget stays installed locally and can be re-added from the picker. ## Widget settings (config dialog) To configure a widget's bound entity, display options, size, or color: tap the gear or edit affordance that appears on the widget while in edit mode. The config dialog opens with fields auto-generated from the widget's schema. Changes save instantly, no apply button. --- title: "Widget CLI" description: "All CLI commands for creating, building, testing, and publishing GlassHome widgets." slug: widget-cli canonical: https://glasshome.app/docs/widget-cli section: "Widget Development" updated: 2026-06-09 --- # Widget CLI The package is `@glasshome/widget-cli` and its binary is `glasshome-widget`. Scaffolded projects pin `@glasshome/widget-cli` in `devDependencies` and add a `widget` script alias in `package.json` (e.g. `"widget": "glasshome-widget"`), which is why every command below is shown as `bun widget `; the alias runs the project-local version, kept current with `bun update`. Outside a scaffolded project use `bunx @glasshome/widget-cli@latest ` instead. The `@latest` tag matters: a bare `bunx @glasshome/widget-cli` can reuse a stale cached version. ## Version requirements Hub enforces a minimum CLI version. The current floor is **0.4.11**, served from `/api/widgets/cli-version`. CLIs at or above **0.4.14** fetch this endpoint on `login` and `publish`, and hard-stop below the floor with an actionable message instead of a cryptic OAuth or HTTP error. CLIs older than 0.4.14 lack the check, so the floor only protects clients that can read it. If you see a "widget CLI is no longer supported" error, update: ```bash # one-off, no install: bunx @glasshome/widget-cli@latest publish # or update the project dev dependency: bun add -D @glasshome/widget-cli@latest ``` ## create ```bash bunx @glasshome/widget-cli@latest create # or, equivalent, runs `create` by default when not inside a project: bunx @glasshome/widget-cli@latest ``` Scaffolds a new widget project with `package.json`, `vite.config.ts`, `tsconfig`, and a starter widget (`create` already prompts for the first widget's details, no extra `add` call needed). Run this outside of an existing project. ## add ```bash bun widget add ``` Adds a new widget to your project. Prompts for a name and description, then creates `src/{name}/index.tsx` and `src/{name}/manifest.json`. ## build ```bash bun widget build ``` Builds all widgets into per-widget, self-contained JS bundles in `dist/`. Also generates `dist/registry.json` with metadata for all widgets. ## validate ```bash bun widget validate [name] ``` Checks manifests, bundle sizes, and registry consistency. Pass a widget name to validate just one, or omit for all. ## info ```bash bun widget info [name] ``` Prints the project header, the local registry summary, and per-widget metadata + bundle size. Useful for sanity-checking before publishing. ## connect ```bash bun widget connect [--re-auth] ``` Connects to a running Dash instance for live testing. Builds all widgets, uploads each bundle to the dashboard API under the `local` scope, registers them with the dashboard, and watches for changes. Widgets hot-reload as you edit. No local server is started (see [Local Testing Workflow](#local-testing-workflow)). Auth: `connect` uses the **OAuth device-code flow** against the local Dash instance. A browser window opens to the Dash approval page; approve it, and the CLI stores a host-scoped bearer token. This is distinct from `login`, which authenticates against GlassHome Hub. Flags: - `--re-auth`: discard stored credentials and force a fresh device-authorization flow. ## publish ```bash bun widget publish [hub-url] [--name ] [--bump keep|patch|minor|major] [--scope ] ``` Validates first, prompts for scope/widget/version (or takes them from flags), then builds and uploads via a presigned R2 URL. Auto-runs `login` if no token is found, so the explicit `login` step is optional. Flags: - `--name `: publish only the named widget instead of prompting. - `--bump keep|patch|minor|major`: pre-pick the version bump (updates the `version` field in `src//manifest.json`). - `--scope `: pre-pick the publishing scope (e.g. `@my-team`). ## login ```bash bun widget login [hub-url] ``` Authenticates with **GlassHome Hub** via OAuth PKCE (not the local Dash instance). Opens a browser, listens on `http://127.0.0.1:9274/callback` for the callback (`localhost` is also accepted; 120 s timeout), stores the resulting token locally. `publish` runs this automatically if no token is present. ## upgrade ```bash bun widget upgrade ``` Syncs the `sdkVersion` field in every `manifest.json` to the SDK version actually installed in your project. The SDK (`@glasshome/widget-sdk`) is published to npm. How the upgrade works depends on your project type: **Standalone project:** Update the `@glasshome/widget-sdk` range in `peerDependencies` (or `devDependencies`) in your `package.json`, run `bun install` to fetch the new version from npm, then run `bun widget upgrade` to sync the manifests and validate compatibility. **Monorepo workspace:** The CLI reads the SDK version from the workspace package and runs `bun install` from the monorepo root to sync, then updates all manifest files. In both cases `bun widget upgrade` re-runs `bun widget validate` afterwards to catch any breaking API changes. ## Local Testing Workflow The `connect` command is how you test widgets against a running Dash instance. Here's what it does: - Builds all widgets in your project. - Runs an **OAuth device-code flow against the Dash URL** (browser opens to the Dash approval page). - Uploads each bundle to the Dash API under the `local` scope. - Registers the widgets via the Dash internal tRPC endpoint and enables dev mode. - Watches `src/` for changes; on save, rebuilds and re-uploads the affected widget. - On `Ctrl+C`, unregisters the widgets and exits. No local HTTP server is started; Dash talks to its own API for the bundle bytes. > **Global flag:** Use `--dir ` with any project-scoped command (`add`, `build`, `validate`, `info`, `connect`, `publish`, `upgrade`) to point at a different widget project directory. `create` ignores it; it scaffolds into a new directory under the current working directory. --- title: "Styling & Animation" description: "How widget styling works in the GlassHome SDK — per-widget CSS bundles, Tailwind utilities, your own CSS files, theme variables, container queries, and animation." slug: widget-styling canonical: https://glasshome.app/docs/widget-styling section: "Widget Development" updated: 2026-06-13 --- # Styling & Animation Each widget renders inside its own **closed shadow root** in the dashboard. That root is cut off from the dashboard stylesheet, so every widget carries its own complete CSS. The build produces one `.css` next to `.js`, and the host adopts it into the widget's shadow root at mount. The isolation runs both ways: your styles never leak into the dashboard or another widget, and the dashboard's styles never leak into you. Everything your widget renders has to be styled by CSS the build put in your bundle. ### Tailwind, built in The full Tailwind v4 utility set plus tw-animate-css. Only the classes you actually use ship in your bundle. ### Your own CSS Import a .css file from your widget and it gets bundled into the widget stylesheet, scoped to your shadow root. ### Theme follows the home The user ## What's in the bundle When you build, the SDK runs Tailwind over exactly three sources and writes the result to your widget's stylesheet: 1. **Your widget's own files** (the directory of your `index.tsx`) — so the utility classes in your JSX are included. 2. **`@glasshome/ui`** — the theme and component styles every GlassHome widget ships with. Keep it as a dependency. 3. **The SDK shell styles** — the `.glasshome-widget-*` classes and CSS variables the `Widget` components use. Tailwind is run with `source(none)`, which disables the automatic project-root scan. The practical effect: each widget gets **only the utilities it uses**, not the superset of every widget in your repo. Classes have to appear literally in your source for Tailwind to see them, so build class names statically rather than concatenating them at runtime. ## Tailwind utilities Use Tailwind exactly as you would anywhere else: ```tsx 22°C ``` `tw-animate-css` is bundled too, so its animation utilities (`animate-in`, `fade-in`, `slide-in-from-bottom`, …) are available without extra setup. ### Dark mode `dark:` variants work. The host mirrors the dashboard's `dark` class onto your shadow host element, and the SDK redefines the `dark` variant to be shadow-aware, so: ```tsx
``` reacts to the dashboard theme. For logic that needs the boolean, use `isDark()` from the SDK. ## Adding your own CSS Import a stylesheet anywhere in your widget's module graph and it is bundled into the same `.css`, scoped to your shadow root: ```tsx // src/my-widget/index.tsx import "./styles.css"; ``` ```css /* src/my-widget/styles.css */ .spark { background: radial-gradient(circle, var(--widget-color), transparent); } @keyframes drift { from { transform: translateX(0); } to { transform: translateX(8px); } } ``` Plain CSS, nesting, custom properties, and `@keyframes` all work. Because the stylesheet lives in your shadow root, your selectors only match your own DOM, so you don't need to namespace class names defensively. ## Theme variables The user's theme reaches your widget by **inheritance**: variables like `--background`, `--foreground`, `--card`, `--primary`, `--border`, `--muted`, `--accent`, and `--radius` are defined on the dashboard document and inherit across the shadow boundary into your root. That is how a widget follows the household theme and live theme changes. Use them, through Tailwind (`bg-card`, `text-foreground`, `rounded-[var(--radius)]`) or directly (`color: var(--foreground)`). > **Never redefine theme variables on :host:** Defining a theme variable on :host in your widget CSS freezes it at build-time and stops the value inheriting, so your widget stops reacting to the theme. The build fails if it finds one of the theme variables above defined under :host. Set your own (`--widget-*` or custom) variables freely; just leave the theme ones to inherit. ## Responsive sizing with container queries There are **no size tiers and no JS measurement** for visual scale. The widget shell is a CSS size container (`container-type: size; container-name: widget`), and the SDK's own padding, gap, icon, and text sizes are fluid functions of that container. Two widgets at the same rendered box look identical, and resizing is smooth. Scale your own content the same way. Tailwind's container-query variants resolve against the `widget` container: ```tsx {time()} ``` Or write the query by hand in your CSS: ```css @container widget (min-aspect-ratio: 1) and (max-height: 149px) { .my-layout { flex-direction: row; } } ``` When you need to branch *rendered content* (not just size) — for example, hiding a forecast strip on a small chip — read the measured box from the context and apply your own pixel thresholds: ```tsx const ctx = useWidgetContext(); const compact = () => ctx.dimensions().width < 200; ``` ## Coloring the shell `` draws a gradient shell from a single accent color. Set it with props: | Prop | Effect | |---|---| | `tone` | Semantic color: `"success" \| "warning" \| "danger" \| "info" \| "neutral" \| "accent"`. Resolves to `--widget-color`. | | `color` | CSS color override for `--widget-color`. Overrides `tone`. | | `colorTo` | Second gradient stop (`--widget-color-to`). | | `gradient` | A full CSS gradient string; replaces the auto-generated shell. | ```tsx ``` ### SDK CSS custom properties These are defined by the SDK on the widget shell and are yours to read (and, except where noted, to override) in your own CSS: | Variable | Meaning | |---|---| | `--widget-color` | The shell's accent color (driven by `tone`/`color`). | | `--widget-color-to` | Second gradient stop. | | `--widget-icon-color` | Per-icon / per-fill accent (set by `Widget.Icon color` / `SliderFill color`). | | `--widget-glow-strength` | Glow intensity multiplier for icons and fills. | | `--widget-fill-value` | Slider fill level, `0`–`100`. | | `--tone-{name}` | The resolved color for each semantic tone. | The fluid scale tokens (`--widget-pad`, `--widget-gap`, `--widget-icon-box`, `--widget-title-size`, `--widget-value-size`, …) are also on the shell; override them on `.glasshome-widget` if you want a denser or looser look than the default. ### SDK shell classes The `Widget` slot components render with these classes. Target them when you want to restyle a slot rather than replace it: | Class | Slot | |---|---| | `.glasshome-widget` | The shell (gradient, border, container context) | | `.glasshome-widget-content` | `Widget.Content` layout wrapper | | `.glasshome-widget-icon` | `Widget.Icon` tile | | `.glasshome-widget-title` | `Widget.Title` | | `.glasshome-widget-status` | `Widget.Status` | | `.glasshome-widget-value` | `Widget.Value` | | `.glasshome-widget-badge` | Title badge | If you ever need the SDK tokens in a root the host didn't set up (a custom mount, a test), call `injectTokens(root)` to attach them. ## Animation Standard CSS and Tailwind animation work inside the shadow root. A few SDK pieces help you keep motion correct and cheap: - **`tw-animate-css` utilities** for one-shot enter/exit animations (`animate-in fade-in`, etc.). - **`loading` prop** on `` renders a pulsing overlay while data is pending: ``. - **`Widget.SliderFill`** animates a 0–100 fill (brightness, volume, …). Pass `isDragging` to drop the transition while the user drags so the fill tracks the finger 1:1. ```tsx ``` ### Respect reduced motion `useReducedMotion()` is a reactive accessor over the user's `prefers-reduced-motion` setting. Gate non-essential animation on it: ```tsx const reduced = useReducedMotion();
``` ### Pause off-screen animation `useIntersectionPause(el)` returns `true` while the element is outside the viewport, so you can stop animating widgets the user can't see: ```tsx let ref: HTMLDivElement | undefined; const paused = useIntersectionPause(() => ref); createEffect(() => { if (paused()) stopTicker(); else startTicker(); }); ``` ## Charts For SVG charts, `svgColors` gives energy-domain colors ready to drop into `fill`/`stroke`. Each key (`solar`, `grid`, `battery`, `ev`, `home`, `positive`, `negative`) exposes `solid`, `stroke`, and a reduced-opacity `fill` for area shading: ```tsx import { svgColors } from "@glasshome/widget-sdk"; ``` For drawing smooth lines, `monotoneCubicPath(points)` returns an SVG path string from a list of points. ## See also ### [Widget SDK reference](/docs/widget-sdk) Components, hooks, props, and the data layer. ### [Widget Development](/docs/widget-development) Scaffold, develop, and connect to a running dashboard. --- title: "Themes" description: "7 free theme presets with light/dark mode. The theme editor (colors, radius, backgrounds, upload) requires Pro." slug: themes canonical: https://glasshome.app/docs/themes section: "Dashboards" updated: 2026-06-09 --- # Themes 7 built-in presets, each with light and dark variants. Apply a preset in **Settings > Theme**. Switching is instant. ## Free vs Pro | | Free | Pro | |---|---|---| | All 7 presets | Yes | Yes | | Light / dark mode | Yes | Yes | | Applying custom themes already on your Dash | Yes | Yes | | Theming: editor (colors, radius), custom backgrounds, theme upload | | Yes | Presets and switching between themes are free for everyone. Custom themes themselves are free to use: if one is already on your Dash, anyone can apply it, and it keeps working if Pro lapses. Creating or editing one (theming) requires [Pro](/docs/pro). ## Presets | Preset | Light / Dark | |---|---| | Midnight Glass | Dark | | Sunrise Studio | Light / Dark | | Forest Zen | Light / Dark | | Lavender Dreams | Light / Dark | | Coral Reef | Light / Dark | | Monochrome Pro | Light / Dark | | Ocean Breeze | Light / Dark | ## Theme editor (Pro) With [Pro](/docs/pro), the editor in **Settings > Theme** unlocks below the preset grid: - **Colors.** 6 slots: primary, secondary, accent, border, card, background. Separate light and dark palettes. - **Border radius.** Slider from 0 to 2.0. Controls the roundness of cards, buttons, and widgets. - **Backgrounds.** Built-in patterns, custom images, overlay opacity, and blur amount. Start from any preset, tweak it, and save it as a custom theme on your Dash. You can also upload a theme file shared by someone else. Custom themes follow the same structure as built-in presets. --- title: "Publishing Widgets" description: "Publish your GlassHome widgets to Hub. Login, validate, version, select scope, and publish." slug: widget-publishing canonical: https://glasshome.app/docs/widget-publishing section: "Widget Development" updated: 2026-06-09 --- # Publishing Widgets Share your widgets with the GlassHome community by publishing them to Hub. ## Publish Flow 1. You only need to run this explicitly if you want to authenticate before publishing. The `publish` command auto-runs login if no token is found, so this step is optional in practice. ```bash bun widget login ``` This runs an OAuth PKCE flow against Hub. A browser opens; the callback lands on `http://127.0.0.1:9274/callback`. The resulting token is stored locally and reused on subsequent publishes. This is separate from `bun widget connect`, which authenticates against your local Dash instance using a device-code flow. 2. Checks manifests, bundle sizes, and SDK version compatibility. Fix any issues before publishing. ```bash bun widget validate ``` 3. The CLI validates first, then prompts you to select which widget to publish, choose a scope, and pick a version bump. It then builds and uploads the bundle to Hub via a presigned R2 URL. ```bash bun widget publish ``` Use flags to skip prompts: - `--name `: publish only the named widget. - `--bump keep|patch|minor|major`: pre-pick the version bump. This updates the `version` field in `src//manifest.json`. - `--scope `: pre-pick the publishing scope. ## Publish lifecycle Publishing is immediate: once the CLI confirms the upload, the version is live in the catalog. There is no review queue. The bundle must pass these checks before publishing is accepted: - **Bundle size limit.** Bundles larger than **2 MB** are rejected with a 400 error. - **Duplicate version.** Re-publishing the same version number returns 409. Versions are immutable once published. - **Scope ownership.** You can only publish under your personal scope or an organization scope where you hold the `owner` or `admin` role. ## Versioning The `version` field lives in `src//manifest.json`. The `--bump` flag increments it automatically. Versions follow semver: - **Patch (0.1.0 → 0.1.1).** Bug fixes, no config changes. - **Minor (0.1.0 → 0.2.0).** New features, backward-compatible config. - **Major (0.2.0 → 1.0.0).** Breaking config changes (bump `configVersion` too and add a `migrate` function; see [Config Migrations](/docs/widget-migrations)). > **On SDK 1.0:** Widgets must target `@glasshome/widget-sdk` 1.x and declare a `capabilities` block, or publish is rejected. See [Upgrading to SDK 1.0](/docs/widget-sdk-1-0). ## Publishing Scopes When you publish, the CLI prompts you to select a publishing scope if you belong to any organizations: ``` ? Select publishing scope: ❯ @your-username (personal) @my-team (organization) ``` Personal scope uses your username. Organization scope uses the org slug. You can create organizations on [GlassHome Hub](/docs/organizations). ## Yank, deprecation, and release notes The database schema tracks `yanked`, `yankedReason`, `deprecationMessage`, and `releaseNotes` per version. There is no user-facing mechanism to yank or deprecate a version yourself yet. If you publish a broken version and need it pulled, contact me directly and I'll yank it. `releaseNotes` will be shown in the widget detail view once that UI is built. ## After Publishing - Your widget appears in the Widget Browser inside GlassHome dashboards. - Users can install it with one click from the widget picker. - The bundle is served from Hub's CDN with immutable caching. - Publish updates by bumping the version and running `publish` again. ## Trust Badges - **Official.** Maintained by GlassHome. Installed without a confirmation dialog. - **Community.** Published by users. Users see a consent dialog before installing. > **Bundle size:** Keep your widget bundles small. The hard limit is 2 MB (rejected at publish time). The CLI shows bundle sizes during `validate` and `info`. Large bundles slow down dashboard loading for everyone. --- title: "API Reference" description: "GlassHome Hub public HTTP API. Versioned widget catalog endpoints for external integrators." slug: api-reference canonical: https://glasshome.app/docs/api-reference section: "Hub" updated: 2026-06-09 --- # API Reference GlassHome Hub exposes a small public HTTP API at `https://glasshome.app` for reading the widget catalog. These endpoints are unauthenticated. The versioned `/api/v1/widgets/*` endpoints are CORS-open; see the CORS note under Conventions for the one exception. ## What's public Only the endpoints documented below are part of the public API and covered by versioning. Everything under `/api/*` (without `/v1/`) is internal: it powers the Hub UI and the `@glasshome/widget-cli`, requires a session, and may change without notice. Do not build integrations against `/api/*`. If you need to publish widgets, use the [Widget CLI](/docs/widget-cli). It handles auth and the publish flow for you. ## API versioning and sunset The `/api/v1/` prefix is the current stable version. When a breaking change to the API is needed, a new prefix (e.g. `/api/v2/`) will be introduced and the old one will be announced for sunset with reasonable notice. ## Conventions - All requests/responses are JSON. - Errors: `{ "error": "" }` with an appropriate HTTP status. - Timestamps: ISO 8601, UTC. - Pagination: page-based (`?page=`, `?limit=`). - CORS: `Access-Control-Allow-Origin: *` on the `/api/v1/widgets/*` endpoints only. The `/api/widgets/{scope}/registry.json` route sits outside the `/v1/` prefix and sends no CORS header, so cross-origin browser fetches against it will fail (server-to-server and same-origin fetches are unaffected). ## `GET /api/v1/widgets/search` Search the public widget registry. ### Query parameters | Parameter | Type | Description | |---|---|---| | `q` | string | Free-text. Matches `displayName` and `description`. | | `compatSdk` | string | SDK semver (e.g. `0.5.2`). Must be a valid semver string or the endpoint returns 400. Adds `latestCompatibleVersion` per result. | | `scope` | string | Restrict to a scope, e.g. `glasshome`. | | `official` | boolean | `true` limits to Official widgets; `false` limits to non-official. | | `page` | number | Page number, default `1`. | | `limit` | number | Page size, default `20`, max `100`. | Unlisted widgets are excluded from results. ### Response ```json { "widgets": [/* WidgetSummary */], "total": 123, "page": 1, "limit": 20 } ``` ### WidgetSummary fields | Field | Type | Description | |---|---|---| | `scope` | string | Publishing scope (e.g. `"glasshome"`) | | `name` | string | Widget identifier | | `displayName` | string | Human-readable display name | | `description` | string or null | Short description | | `icon` | string or null | Iconify icon identifier | | `isOfficial` | boolean or null | Whether this is an Official (GlassHome-maintained) widget | | `downloadCount` | number or null | Cumulative installs | | `latestVersion` | VersionEntry or null | Latest non-yanked version. `null` if no published version exists. | | `latestCompatibleVersion` | VersionEntry or null | Only present when `compatSdk` is supplied. Newest version satisfying the requested SDK range, or `null`. | ### VersionEntry fields | Field | Type | Description | |---|---|---| | `scope` | string | Scope (same as parent) | | `name` | string | Widget name (same as parent) | | `version` | string | Semver version string | | `bundleUrl` | string | CDN URL for the JS bundle | | `bundleSize` | number | Bundle size in bytes (raw, not gzip) | | `sha256Hash` | string | SHA-256 hex digest of the bundle | | `publishedAt` | string | ISO 8601 timestamp | | `yanked` | boolean | Whether this version has been yanked | | `yankedReason` | string or null | Human-readable reason if yanked | | `deprecationMessage` | string or null | Warning shown on widgets using this version | | `releaseNotes` | string or null | User-facing changelog text | | `displayName` | string | From version manifest, falls back to widget row | | `description` | string or null | From version manifest, falls back to widget row | | `icon` | string or null | From version manifest, falls back to widget row | | `minSize` | `{w, h}` | Minimum grid size | | `maxSize` | `{w, h}` | Maximum grid size | | `sdkVersion` | string | SDK version range the bundle was built against | | `configVersion` | number or null | Config version for migration support | | `permissions` | string[] | Declared permissions | | `license` | string or null | License identifier | ### Error responses | Status | Condition | |---|---| | `400` | `compatSdk` is not a valid semver string | ## `GET /api/v1/widgets/{scope}/{name}` Single widget detail, including the full version list. ### Response ```json { "widget": { "scope": "glasshome", "name": "my-widget", "displayName": "My Widget", "description": "...", "icon": "mdi:lightbulb", "isOfficial": false, "downloadCount": 42, "createdAt": "2025-01-01T00:00:00.000Z" }, "versions": [/* VersionEntry (see above) */], "latestVersion": "1.2.0" } ``` `versions` is ordered by `publishedAt` descending. `latestVersion` is the version string of the newest non-yanked published version, or `null` if none exists. ### Error responses | Status | Condition | |---|---| | `404` | Widget not found or is unlisted | ## `POST /api/v1/widgets/{scope}/{name}` Increments the widget's download counter. Used by dashboards on install. No request body, no authentication required. This is a best-effort counter. It uses a simple `+1` SQL update with no rate-limiting or deduplication in the current implementation. Do not rely on it for precise install analytics. ### Response ```json { "success": true } ``` ### Error responses | Status | Condition | |---|---| | `404` | Widget not found | ## `GET /api/widgets/{scope}/registry.json` Static-shaped registry for a single scope. The dashboard uses this for fast bulk fetch on first load. Suitable for edge caching (no `Cache-Control` set by the route itself). No CORS header (see Conventions): fetch it server-to-server, not from a cross-origin browser context. ## Operational endpoints A few unauthenticated endpoints exist outside the `/v1/` namespace for tooling and status checks: `GET /api/health`, `GET /api/version` (latest Hub release), `GET /api/widgets/cli-version` (minimum supported `widget-cli` version), `GET /api/stats`, and `GET /api/settings`. They are operational, not versioned, and carry no stability guarantee or CORS header. Use them for status/update checks, not as an integration surface. ## Rate limits Currently unmetered. Be reasonable; aggressive scraping may get rate-limited later with `429` + `Retry-After`. ## Reporting issues [Bug Reports](/docs/bug-reports). Include the full request (URL + payload) and the response (status + body). --- title: "Remote Access" description: "Access your Dash from outside your home network. VPN works today; managed tunnel is in progress." slug: remote-access canonical: https://glasshome.app/docs/remote-access section: "Dashboards" updated: 2026-06-09 --- # Remote Access How to reach your Dash from outside your home network. ## What works today: VPN A VPN (WireGuard, Tailscale, or similar) is the recommended way to access Dash remotely right now. Once your device joins the home network via VPN, Dash is reachable exactly as it is locally: open `http://:3123` as usual. No extra Dash configuration is needed. The VPN handles routing. ## Coming: managed tunnel > **In progress:** The built-in managed tunnel is still under active development and not yet available. The section below describes how it will work when it ships. When the managed tunnel ships, it will use **Cloudflare Tunnel** (cloudflared) to expose your Dash without port forwarding or a VPN. Hub will manage the tunnel lifecycle (provisioning, DNS). You will pick a subdomain (e.g. `my-home.glasshome.cloud`) and Dash will be reachable from anywhere at that address. How it will work: - `cloudflared` runs alongside the Dash API as a background process. - Traffic is encrypted end-to-end between your device and Cloudflare. - Hub provisions the subdomain and manages the DNS record. - No incoming ports are opened on your network. A Hub account and an internet-connected host (Linux x64/arm64, macOS, or Windows x64) will be required. ## Troubleshooting For tunnel-related issues (once the feature ships), see [Troubleshooting](/docs/troubleshooting#tunnel). ## Related docs - [Connecting to Home Assistant](/docs/connecting): local pairing comes first - [Concepts](/docs/concepts): how Dash, HA, and Hub relate - [FAQ](/docs/faq): privacy and account questions --- title: "Config Migrations" description: "Handle breaking widget config changes across versions with configVersion and migrate functions." slug: widget-migrations canonical: https://glasshome.app/docs/widget-migrations section: "Widget Development" updated: 2026-06-09 --- # Config Migrations When you change a widget's config shape, existing users still have the old config saved in their dashboard. Migrations let you transform old configs into the new shape without breaking anything. ## How It Works ### Bump configVersion Increment the configVersion number in your manifest when you make a breaking config change. ### Add a migrate function The migrate function receives the old config and the version it was saved at, and returns the new shape. ### Dashboard runs it automatically When the user The `fromConfigVersion` argument passed to your `migrate` function is the integer stored in the user's saved config. If the config was saved before `configVersion` was ever set, the dashboard treats the stored version as **1** (and also treats `manifest.configVersion` as 1 when omitted). In practice: if you add `configVersion: 2` to an existing widget for the first time, `fromConfigVersion` will be `1` for all existing users, so your migration should check `fromVersion < 2`. ## Example Say your widget originally had a single `entity` string field, and you're changing it to an `entityId` field with an additional `showLabel` boolean. ```tsx // src/my-widget/index.tsx import { defineWidget } from "@glasshome/widget-sdk"; import { z } from "zod"; // v1 schema, kept around for safe parsing during migration const configSchemaV1 = z.object({ entity: z.string(), }); // v2 schema (current) const configSchema = z.object({ entityId: z.string(), showLabel: z.boolean().default(true), }); type Config = z.infer; function MyWidget(props: { config: Config }) { return (
{props.config.entityId}
); } export default defineWidget({ manifest: { name: "My Widget", sdkVersion: "^0.5.0", minSize: { w: 1, h: 1 }, maxSize: { w: 4, h: 4 }, configVersion: 2, }, configSchema, migrate(oldConfig, fromVersion) { if (fromVersion < 2) { const parsed = configSchemaV1.parse(oldConfig); return { entityId: parsed.entity, showLabel: true, }; } return oldConfig; }, component: MyWidget, }); ``` ## Testing migrations locally The safest way to test a `migrate` function is to unit-test it in isolation: call it with example old config objects and assert the output shape. Because `migrate` is a pure function (no side effects, no SDK imports required), a plain test file with `bun test` works without any dashboard involved. For end-to-end testing, run `bun widget connect ` against a Dash instance that has a widget placement with old config. The dashboard calls your `migrate` function live, and you can inspect the result in the widget's settings panel. ## Guidelines - **Always increment, never reset.** `configVersion` should only go up. When omitted or missing from saved config, it is treated as 1. Bump by 1 for each breaking change. - **Handle all previous versions.** Your `migrate` function might receive configs from any previous version. Check `fromVersion` and handle each case. - **Return a complete config.** The returned object should match your current config schema. Missing fields will use Zod defaults if you have them. - **Non-breaking changes don't need a migration.** Adding a new optional field with a Zod default doesn't require bumping `configVersion`. Only bump when you rename, remove, or change the type of an existing field. > **No migration needed?:** If you're only adding new optional fields with defaults in your Zod schema, you don't need `configVersion` or `migrate` at all. Zod fills in the defaults automatically. --- title: "Upgrading to SDK 1.0" description: "What changed in widget-sdk 1.0 — shadow-root rendering, separate CSS, capability declarations, and no direct Home Assistant access — and how to migrate a widget." slug: widget-sdk-1-0 canonical: https://glasshome.app/docs/widget-sdk-1-0 section: "Widget Development" updated: 2026-06-12 --- # Upgrading to SDK 1.0 `@glasshome/widget-sdk` 1.0 is the security release. Widgets now run inside an isolated, permission-scoped sandbox. Most widgets that only use SDK hooks need small, mechanical changes. This page is the migration checklist. > **Info:** Dash only loads widgets built against SDK 1.x. A widget published against an older SDK shows a **"needs update"** tile until you republish. Bump your `@glasshome/widget-sdk` dependency to `1.x`, work through this page, then `bun widget publish`. ## What changed ### Shadow-root rendering Each widget renders in its own closed shadow root for style and DOM isolation. Your CSS no longer leaks into Dash or other widgets, and vice versa. ### Separate stylesheet The build emits index.css next to index.js. Dash adopts it into your widget ### Capability declarations Your manifest declares which Home Assistant domains the widget reads or controls. The user approves them at install; the host enforces them. ### No direct HA access Widgets no longer import @glasshome/sync-layer or call fetch/WebSocket directly. All Home Assistant interaction goes through SDK hooks, which are permission-checked. ## Migration checklist ### 1. Bump the SDK dependency ```jsonc // package.json "peerDependencies": { "@glasshome/widget-sdk": "1.0.0" } ``` And set the manifest range so Dash knows the widget targets 1.x: ```jsonc // manifest.json "sdkVersion": "^1.0.0" ``` ### 2. Import data and service hooks from the SDK, not sync-layer Direct `@glasshome/sync-layer` imports now **fail the build** with a clear error. The SDK re-exports every hook you used: ```diff - import { useEntity, useService } from "@glasshome/sync-layer/solid"; + import { useEntity, useService } from "@glasshome/widget-sdk"; ``` This is what makes permissions enforceable: service calls route through the host's permission check instead of straight to Home Assistant. ### 3. Declare capabilities Add a `capabilities` array to your manifest listing the Home Assistant domains your widget touches, `control` for ones you operate and `read` for ones you only display. A widget that never touches Home Assistant declares `[]`. Publishing a 1.x widget without the array is rejected. ```jsonc // manifest.json "capabilities": [ { "domain": "light", "access": "control" }, { "domain": "sensor", "access": "read" } ] ``` The full grammar (access levels, narrowing to specific entities or services, what the user approves, and how the host enforces it) is covered in **[Capabilities & Permissions](/docs/widget-capabilities)**. Declare the minimum: a widening update has to be re-approved by the user. ### 4. Don't fight the shadow root Your widget now renders in an isolated shadow root with its own CSS bundle. Theme variables still inherit, so don't redefine them; keep `@glasshome/ui` as a dependency; and reach Home Assistant through SDK hooks rather than `window`/`document` globals. The full styling model is in **[Styling & Animation](/docs/widget-styling)**. ### 5. Rebuild and publish ```bash bun run build # emits index.js + index.css + an updated registry bun widget publish --scope ``` The publish flow verifies the bundle hash server-side and validates your capability declaration before the version goes live. ## Testing locally first `bun widget connect ` builds and serves your widget to a running Dash, including the capability flow: install it, and you'll see the same permission prompt your users will. Confirm the widget renders correctly inside its shadow root (styling, theme, dialogs) and that its service calls work before publishing.