---
title: "Getting Started"
description: "Build custom GlassHome widgets with SolidJS and the widget SDK. Scaffold, develop with hot-reload, validate, and publish."
canonical: https://glasshome.app/docs/widget-development
section: "Build Widgets"
updated: 2026-06-27
---
# Getting Started

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 <dashboard-url>` rebuilds on save and re-uploads to a running dashboard.
  ### Publish

`bun widget publish` to Hub. Pick personal or organization scope.

The `bun widget <cmd>` 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 <cmd>`. 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 <project-name>
       ```
       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/<name>/index.tsx` and `src/<name>/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. Read entities with `useEntity` / `useEntities` (from the ids the user picked in config) and the SDK keeps them reactive. `useWidgetEntityGroup` then aggregates them and supplies an empty state.
- You do *not* hold a Home Assistant connection or token. All reads and service calls go through SDK hooks and are checked against your declared [capabilities](/docs/widget-capabilities).

## 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 guide](/docs/widget-sdk)

defineWidget, Widget components, hooks, the HA data layer, entity helpers.
  ### [API Reference](/docs/widget-api-reference)

Every public export across the SDK, /schemas, and /vite entry points.
  ### [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 <command>` 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.