# DockHub widget SDK 1.0
Build a DockHub widget with HTML, CSS and JavaScript. It runs in Microsoft Edge WebView2 inside a dock card, follows the dock's theme, and talks to DockHub through a small `window.dockhub` API. No C#, no build step.
**SDK 1.0** is `window.dockhub` with `apiVersion` 2 and the manifest format below. It is stable:
- Nothing in SDK 1.0 is removed or changes meaning within DockHub 1.x. New methods, manifest fields and events may be added; check `dockhub.apiVersion` (or that a method exists) before using one added later.
- If something has to go, it is first marked deprecated in this page and in [`sdk/types/dockhub.d.ts`](../sdk/types/dockhub.d.ts), keeps working for at least one more major version of DockHub, and the release notes say so.
- A widget that needs a newer DockHub sets `minDockHubVersion`; older DockHub versions refuse to install it instead of running it half working.
## Tools
- **TypeScript types:** [`sdk/types/dockhub.d.ts`](../sdk/types/dockhub.d.ts) describes all of `window.dockhub`. Copy it next to your widget and start your script with `// @ts-check` and `/// ` for completion and type checking in VS Code (or any editor with TypeScript), even in plain JavaScript. The samples are checked against it (`node sdk/check-samples.mjs`, which needs `npm install -g typescript`).
- **dockhub-widget:** [`tools/dockhub-widget`](../tools/dockhub-widget/dockhub-widget.mjs), a single Node.js file without dependencies (Node 18 or newer):
```
node dockhub-widget.mjs create com.example.my-widget # a new widget: manifest, page, script, types
node dockhub-widget.mjs validate my-widget # the checks DockHub makes when it installs it
node dockhub-widget.mjs pack my-widget # my-widget -> com.example.my-widget-1.0.0.dockwidget
```
`validate` makes the same checks as DockHub (id, name, entry, server settings, paths, package limits) and warns about things that probably don't work as meant, such as a file missing from `files` (it wouldn't be there when installed from a link). `pack` refuses a widget DockHub would refuse and leaves editor files (`dockhub.d.ts`, `jsconfig.json`, dot files) out of the package.
Three complete examples live in [`samples/widgets`](../samples/widgets):
- `hello-world`: settings, per-widget storage, a context menu item and a notification.
- `github-stars`: network access to one declared host.
- `github-pulls`: pull requests waiting for your review, your open pull requests or unread notifications, with an optional token and a notification for new items.
All three are also listed under *Settings › Widget gallery › Featured web widgets* and install with one click.
## Try it in two minutes
1. Copy `samples/widgets/hello-world` to `%AppData%\DockHub\widgets\hello-world`.
2. Restart DockHub (tray menu › Restart, or *Settings › Backup and troubleshooting › Restart DockHub*).
3. Open *Settings › Widget gallery*: your widget is listed under **Web widgets**. Press **+**.
While developing, set `"debugLogging": true` in `%AppData%\DockHub\config.json` and restart DockHub. Then:
- **Live reload.** Saving any file in `%AppData%\DockHub\widgets\` reloads the widget on the dock within half a second. A changed `manifest.json` is read again (new variants, settings and permissions), so you don't have to reinstall or restart.
- The widget's right-click menu has **Developer tools** (the Edge DevTools for that widget). **Reload** is always there.
## Package and share
A `.dockwidget` file is a zip of the widget folder (the files can be at the zip's root or inside one folder). Users install it by opening the file (the installer registers the `.dockwidget` type, so a double-click in File Explorer works) or with *Settings › Widget gallery › Install widget…*. DockHub shows the name, version, author and the permissions before installing. Installing a package with the same `id` replaces the older version and keeps each widget's settings and storage.
A package can be up to 20 MB, 50 MB unpacked and 256 files; no entry may point outside the widget's folder. From a command line: `DockHub.exe --install-widget path\to\widget.dockwidget` (it also works while DockHub is running).
You can also share a link. *Settings › Widget gallery › Install from link…* accepts:
- a link to a `.dockwidget` or `.zip` file (for example a GitHub release asset),
- a link to a `manifest.json`: DockHub downloads the `entry` file and everything listed in `files` from the same folder,
- a GitHub folder link such as `https://github.com/you/widgets/tree/main/pomodoro-plus`.
Links must use HTTPS. Every file must sit next to `manifest.json` (no `..`, no other hosts), a single file can be up to 5 MB and a package up to 20 MB. The same permission prompt appears before anything is installed.
## manifest.json
```json
{
"id": "com.example.pomodoro-plus",
"name": "Pomodoro+",
"version": "1.0.0",
"author": "Your name",
"description": "What the widget shows, in one or two sentences.",
"entry": "index.html",
"minDockHubVersion": "0.7.0",
"variants": [
{ "id": "standard", "name": "Standard", "size": "standard" },
{ "id": "mini", "name": "Mini", "size": "compact" }
],
"settings": [
{ "key": "minutes", "type": "number", "label": "Focus length", "default": 25, "min": 5, "max": 90 },
{ "key": "sound", "type": "toggle", "label": "Play a sound", "default": true },
{ "key": "mode", "type": "choice", "label": "Mode", "options": ["Work", "Study"], "default": "Work" },
{ "key": "label", "type": "text", "label": "Label", "description": "Shown under the timer." }
],
"permissions": { "network": ["api.example.com", "*.example.org"], "notifications": true },
"files": ["style.css", "img/tomato.svg"]
}
```
| Field | Notes |
|---|---|
| `id` | Lower-case letters, digits, dots and dashes (3 to 64 characters). Reverse domain style is recommended. |
| `entry` | The HTML file to load, relative to the widget folder. |
| `variants` | Layouts users pick from the widget's *Appearance* menu. `size` is `compact` (44×44), `standard` (170×44) or `wide` (260×44), in dock units; it also picks the card's width class when *Even widget widths* is on. |
| `settings` | Shown in *Settings › Dock items* when the widget is selected. Types: `text`, `number` (`min`, `max`), `toggle`, `choice` (`options`). |
| `permissions.network` | The only hosts `fetch`, images and scripts may load from (HTTPS or WSS). Everything else is blocked. `*.example.org` also matches `example.org`. |
| `permissions.networkFromSettings` | Keys of `text` settings that hold a server address, such as a Home Assistant URL. The host the user enters there is allowed too, and `dockhub.http.request` may use plain `http` for it. The install prompt says so. These settings can't have a `default`: the address always comes from the user. |
| `permissions.notifications` | Allows `dockhub.notify`. |
| `files` | Optional. Other files of the widget (relative paths) that DockHub downloads when the widget is installed from a `manifest.json` link. Packages don't need it. |
## The widget's page
- Files are served from a private `https://.widget.dockhub/` address. There is no access to local files, other sites' cookies, downloads, pop-up windows, the camera, the microphone or location.
- The page background is transparent: the dock card shows through. Size your content to the variant's size and don't scroll.
- The card keeps a one-pixel frame around the page and lights it up while the pointer is over your widget, like any other card; use CSS `:hover` for hover effects inside the page.
- Before your scripts run, DockHub sets these CSS variables on `` and keeps them in sync with the dock's theme:
| Variable | Meaning |
|---|---|
| `--dh-text` | Primary text color |
| `--dh-text-secondary` | Secondary text color |
| `--dh-accent` | Windows accent color |
| `--dh-card` | Card background |
| `--dh-font` | Segoe UI Variable font stack |
`` (or `light`) is set too.
## window.dockhub
All calls return promises. `apiVersion` is `2`: SDK 1.0 (DockHub 0.9 added `http.request`). The types are in [`sdk/types/dockhub.d.ts`](../sdk/types/dockhub.d.ts).
```ts
dockhub.settings.get(): Promise> // manifest defaults + the user's values
dockhub.settings.onChange(handler: (values) => void)
dockhub.storage.get(key: string): Promise // per widget copy, survives restarts
dockhub.storage.set(key: string, value: unknown): Promise // 256 KB per widget copy
dockhub.notify({ title, body }): Promise // needs permissions.notifications
dockhub.openUrl(url: string): Promise // http(s) only, opens the default browser
dockhub.http.request({ url, method?, headers?, body? }): Promise<{ status, ok, contentType, body }>
// made by DockHub, not the page (see below)
dockhub.contextMenu.set(items: { id: string, label: string }[]): Promise // up to 8 items
dockhub.contextMenu.onSelect(handler: (id: string) => void)
dockhub.onTheme(handler: ({ mode, vars }) => void)
dockhub.onSize(handler: (size: 'compact' | 'standard' | 'wide') => void)
dockhub.size // current size
```
Unknown methods, missing permissions and full storage reject the promise with an `Error`.
### dockhub.http.request
`fetch` in the page is limited by the browser: the page is served over https, so plain `http://` addresses are
blocked, and the server must allow the widget's origin (CORS). A server on your network, such as Home Assistant at
`http://homeassistant.local:8123`, usually does neither. `dockhub.http.request` makes the request from DockHub
instead:
- the host must be in `permissions.network` or come from a `permissions.networkFromSettings` setting;
- methods `GET`, `POST`, `PUT`, `DELETE`; request bodies up to 64 KB (text), answers up to 1 MB (returned as text);
- no cookies, no redirects (a 3xx answer is returned as it is), a 15-second timeout;
- `Host`, `Cookie` and connection headers can't be set.
```js
const response = await dockhub.http.request({
url: `${settings.server}/api/states/sensor.outside`,
headers: { Authorization: `Bearer ${settings.token}` },
});
if (response.ok) show(JSON.parse(response.body).state);
```
See `samples/widgets/home-assistant` for a complete widget.
## Updates
A widget installed from a link (a `manifest.json`, a GitHub folder or a `.dockwidget` link) remembers where it came from, in `source.json` in its folder. Once a day DockHub reads the `manifest.json` behind that link, and when its `version` is newer than the installed one (and the widget doesn't need a newer DockHub), it shows a notification and an **Update** button in *Settings › Widget gallery*.
- Raise `version` in `manifest.json` for every release. Versions are compared part by part (`1.2.10` is newer than `1.2.9`), and a pre-release such as `1.3.0-beta.1` comes before `1.3.0`.
- An update keeps every copy's settings and storage. If the new version asks for more than the installed one (a new host, a server setting or notifications), DockHub lists what is new and asks first.
- Widgets installed from a `.dockwidget` link or file aren't checked (their version is only known after downloading the whole package); share a `manifest.json` or GitHub folder link if you want your users to get updates.
## Share it in the gallery
*Settings › Widget gallery* lists the community widgets from [`samples/widgets/index.json`](../samples/widgets/index.json),
fetched once a day. To add yours, open a pull request with one entry:
```json
{ "id": "com.example.pomodoro-plus", "name": "Pomodoro+", "author": "Your name",
"description": "One sentence, at most 200 characters.",
"link": "https://github.com/you/widgets/tree/main/pomodoro-plus" }
```
`id` must match your manifest, and `link` is anything *Install from link* accepts (a GitHub folder, a `manifest.json`
or a `.dockwidget` file). DockHub shows the widget's permissions before it installs anything.
## Good practice
- Keep the page light: every widget is part of a shared WebView2 process, but timers and animations still cost battery. Use `setInterval` sparingly and pause work when nothing changes.
- Show something useful at 44 DIP height. Put details in the tooltip (`title` attribute) or open a web page with `dockhub.openUrl`.
- Follow the theme variables instead of hard-coded colors, so the widget looks right in light, dark and high-contrast themes.
- Ask only for the hosts you need. Users see the list before installing.