Manifest reference
Every manifest.json field: identity, versions, entry, sizing, permissions, settings, popout, and bundled fonts.
manifest.json sits at the root of the widget folder and declares everything
about the widget up front: identity, what it may touch, its settings, and its
size on the grid. The loader is strict: unknown fields are rejected, and an
invalid manifest quarantines the widget with a reason instead of loading it.
A minimal manifest:
{
"$schema": "./.deskdash/manifest.schema.json",
"id": "clock",
"name": "Clock",
"version": "0.1.0",
"apiVersion": 1,
"permissions": { "time": true },
"defaultSize": { "w": 4, "h": 2 }
}Top-level fields
| Field | Type | Required | Default | Meaning |
|---|---|---|---|---|
$schema | string | no | none | Editor tooling metadata (the dev kit writes ./.deskdash/manifest.schema.json). Accepted and ignored by the app. |
id | string | yes | The widget id. Lowercase letters, digits, . and -, starting with a letter or digit. Must equal the folder name. | |
name | string | yes | Display name. | |
version | string | yes | The widget version. The app treats it as opaque; the marketplace requires dotted numbers (like 1.2.3) and a strictly higher version on every update. | |
apiVersion | number | yes | The bridge API contract the widget was written against (currently 1). See errors and versioning. | |
minAppVersion | string | no | none | "This widget needs DeskDash x.y.z or newer." A feature floor, checked before anything else, so a too-old app shows a friendly message instead of a parse error. |
entry | string | no | index.html | The widget document, a file inside the widget folder. Relative paths only: no leading /, no .., no \. |
description | string | no | "" | Short description. |
author | string | no | "" | Author name. Marketplace installs get this stamped by the store. |
permissions | object | no | all off | What the widget may touch. See the permissions reference. |
interactive | boolean | no | false | The widget wants real mouse input: while the cursor is over it, the window captures the mouse. Non-interactive widgets can never be clicked. |
settings | array | no | [] | Setting fields and preset names, mixed. See below. |
defaultSize | { w, h } | no | { "w": 4, "h": 2 } | Size in grid cells when the widget is added. Each dimension 1..64. |
minSize | { w, h } | no | { "w": 1, "h": 1 } | The smallest the user can resize it to. Each dimension 1..64. |
popout | object | no | none | Declares the popout capability. See below. |
fonts | object | no | none | Bundled font pins. Needs "minAppVersion": "0.2.0". See below. |
Settings
Each settings entry is either a full field object or a preset name string;
the array accepts both, mixed:
"settings": [
{ "key": "title", "type": "text", "label": "Title", "default": "Tasks" },
"panelStyle"
]Field objects
| Key | Type | Required | Meaning |
|---|---|---|---|
key | string | yes | The name your code reads the value under. |
type | string | yes | One of the field types below. |
label | string | yes | Shown in the settings dialog. |
default | any | no | The value before the user edits anything. Ignored for secretRef, filePath, and instances (see below). |
options | string[] | no | The choices for a select. A select with no default falls back to its first option. |
min, max | number | no | Bounds for a number. |
placeholder | string | no | Hint text for empty text and filePath inputs. Needs "minAppVersion": "0.1.10". |
pick | "file" or "folder" | no | Adds a Browse button beside a filePath input and controls what it picks. Needs "minAppVersion": "0.1.10". |
Field types
| Type | The user gets | Your code gets |
|---|---|---|
text | a text input | string |
number | a number input (min/max bounds) | number |
boolean | a toggle | boolean |
select | a dropdown of options | string |
color | a color picker | string |
secretRef | a picker over their stored secrets | a name usable in dd.http.fetch secrets; never the value |
filePath | a path input (plus Browse with pick) | nothing directly; used as settingKey in dd.files, dd.folders, dd.dialogs, dd.claudeUsage |
instances | a picker over other widget instances | nothing directly; feeds dd.widgets |
The three consent-bearing types (secretRef, filePath, instances) never
inherit a manifest default. Their value grants access to something the user
owns (a credential, a file, other widgets), so only a value the user actively
picked counts; a published widget cannot ship pre-pointed at anyone's
private data.
Setting presets
A preset bundles a recurring field (or field pair) with the code that applies it, declared by name:
| Preset | Expands to | Applied by |
|---|---|---|
panelStyle | a Panel style select (none / solid / transparent, default solid, key panel) | <dd-panel settings="panelStyle"> toggles its transparent / bare attributes; zero widget code |
accentColor | a Use theme accent color toggle (key useAccent, default on) plus a Custom color picker (key color) | your code, via dd.theme.accent(settings) or dd.theme.bindAccent(cb); it has no component-side auto-apply |
An unknown preset name quarantines the manifest. To keep a preset's behavior
but change its default, declare the field inline with the same key and your
default, and still put the preset name on the component; it keys off the
setting value regardless of how the field was declared.
Popout
"popout": { "max": { "w": 8, "h": 6 }, "entry": "pane.html" }Presence declares that the widget may temporarily extend past its grid rect
via dd.popout.open. It
is a geometry capability like interactive, not a privacy permission: the
pane is a second sandboxed document with the same grants.
| Key | Meaning |
|---|---|
max | Caps the pane size, in base grid cells like defaultSize. Each dimension 1..64. |
entry | The pane document, a file in the widget folder (same path rules as entry). |
Fonts
Optional, and it needs "minAppVersion": "0.2.0" (older apps do not know the
key). Pins a font onto one or more of the three face tokens, in this
widget's documents only:
"fonts": { "use": { "display": "audiowide" } }use takes any of sans, display, mono. Values are bundled catalog ids
(audiowide, nunito, nunito-sans; an unknown id quarantines the
manifest) or a self: reference to a family the widget ships itself:
"fonts": {
"bundle": [{ "id": "heading", "files": [{ "src": "fonts/mine.woff2", "weight": "400" }] }],
"use": { "display": "self:heading" }
}| Key | Meaning |
|---|---|
bundle | Families shipped in the widget folder. Each has an id (for self: references) and 1..6 files. |
bundle[].files[].src | Path inside the widget folder, charset A-Za-z0-9._/-. |
bundle[].files[].weight | One value or a variable range ("200 1000"), each within 1..1000. Default "400". |
bundle[].files[].style | "normal" (default) or "italic". |
use | Optional pins: sans, display, mono, each a catalog id or self:<bundle id>. |
Rules, enforced at manifest load and again by the marketplace validators: woff2 only (sniffed by magic number, never by extension); at most 4 families, 6 files per family, 2 MB per file, and 4 MB of fonts per widget. The host generates all resulting CSS itself and names families by index, so nothing a manifest says ever reaches a stylesheet.
The pin wins over themes and user overrides inside your documents. Your CSS
keeps using the token (font-family: var(--dd-font-display)). Two notes:
dd.theme.tokens() does not see pins, dd.ui.token() does, so canvas
drawing must resolve faces through
dd.ui.token(). Every bundled catalog face
also exists as a convenience var (--dd-font-audiowide, --dd-font-nunito,
--dd-font-nunito-sans), so a select setting can offer a font choice
without a literal family name in widget code.
Publishing note: the marketplace requires every font-shaped file in the
folder to be declared in fonts.bundle, holds font-carrying submissions for
licensing review, and requires the font's license to permit embedding and
redistribution, with the license text included in the widget.
Validation
What the loader enforces, in order:
minAppVersion, when present, must be a string that parses asx.y.z, and the running app must satisfy it. A too-old app reports "needs DeskDash x.y.z or newer".apiVersionnewer than the host reports "requires a newer dashboard".- The strict parse: unknown fields anywhere are rejected.
idmust match^[a-z0-9][a-z0-9.-]*$and equal the folder name.entry(andpopout.entry) must be a relative path inside the widget folder: no leading/, no.., no\.- Sizes (
defaultSize,minSize,popout.max) must each be 1..64 cells per dimension. fonts, when present, is validated file by file (existence, format, caps) before the widget loads. Fail closed: a bad font spec quarantines the manifest.
A quarantined widget shows up with its reason in the widget list; it never takes the rest of the dashboard down.