---
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/widgets/widget-development
section: "Build Widgets"
updated: 2026-09-28
---
# 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.

Inside a scaffolded project, `bun widget <cmd>` works (the CLI is pinned in `devDependencies`, with a `widget` script alias to `glasshome-widget`); elsewhere use `bunx @glasshome/widget-cli@latest <cmd>`. Full command list: [Widget CLI](/docs/widgets/widget-cli).

## 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. **Scaffold a project**

   ```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. **(Optional) add more widgets**

   ```bash
   bun widget add
   ```

   Prompts for name and description, creates `src/<name>/index.tsx` and `src/<name>/manifest.json`.

3. **Connect to a running Dash instance**

   ```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, then watches `src/`, re-uploading on save. With **Developer Mode** on in Dash Settings, widgets refresh without a page reload.

4. **Publish**

   ```bash
   bun widget publish
   ```

   `publish` runs `login` automatically the first time. See [Publishing](/docs/widgets/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 handles rendering, gestures, theming, entity subscriptions, settings forms, and layout. Your widget supplies how the bound entities are visualized and what the primary tap does.
- No settings UI to write: define a Zod schema; the dashboard renders the form.
- No WebSocket state to manage: `useEntity` / `useEntities` read the entity ids the user picked in config and stay reactive; `useWidgetEntityGroup` aggregates them and supplies an empty state.
- No Home Assistant connection or token to hold: all reads and service calls go through SDK hooks, checked against your declared [capabilities](/docs/widgets/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` (bumps the dep and rewrites the range) | 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                       |

## Building with a coding agent

Claude Code, Codex, Cursor and other coding agents build widgets that match the official ones when they read the widget guide. It ships inside `@glasshome/widget-sdk` at `node_modules/@glasshome/widget-sdk/guide/widgets.md`, so it always describes the SDK your project builds against.

`bun widget create` points your agent at it: a Claude skill in `.claude/skills/glasshome-widget/` and a section in `AGENTS.md`. Both only point at the guide, so a new SDK brings new guidance without touching them. In an existing project, `bun widget upgrade` adds them; it adds one marked section to an `AGENTS.md` you already have and leaves the rest alone.

`bun widget build` tells you when a newer SDK is out. Run `bun widget upgrade` and your agent reads the new guide on its next task.

## Auth flows: connect vs. login

Both flows open a browser, but they authenticate against different hosts, and the resulting tokens are stored separately, never interchangeable:

## Where to go next

### [Widget SDK guide](/docs/widgets/widget-sdk)

defineWidget, Widget components, hooks, the HA data layer, entity helpers.

### [API Reference](/docs/widgets/widget-api-reference)

Every public export across the SDK, /schemas, and /vite entry points.

### [Capabilities & Permissions](/docs/widgets/widget-capabilities)

Declare the Home Assistant access your widget needs and how the user approves it.

### [Styling & Animation](/docs/widgets/widget-styling)

Per-widget CSS bundles, Tailwind, your own CSS files, theme variables, container queries, animation.

### [CLI reference](/docs/widgets/widget-cli)

Every \`bun widget \<command>\` and what it does.

### [Publishing](/docs/widgets/widget-publishing)

Auth, scopes, versioning, trust badges.

### [Config migrations](/docs/widgets/widget-migrations)

Handle breaking config changes without breaking users.