Reference

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

FieldTypeRequiredDefaultMeaning
$schemastringnononeEditor tooling metadata (the dev kit writes ./.deskdash/manifest.schema.json). Accepted and ignored by the app.
idstringyesThe widget id. Lowercase letters, digits, . and -, starting with a letter or digit. Must equal the folder name.
namestringyesDisplay name.
versionstringyesThe 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.
apiVersionnumberyesThe bridge API contract the widget was written against (currently 1). See errors and versioning.
minAppVersionstringnonone"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.
entrystringnoindex.htmlThe widget document, a file inside the widget folder. Relative paths only: no leading /, no .., no \.
descriptionstringno""Short description.
authorstringno""Author name. Marketplace installs get this stamped by the store.
permissionsobjectnoall offWhat the widget may touch. See the permissions reference.
interactivebooleannofalseThe widget wants real mouse input: while the cursor is over it, the window captures the mouse. Non-interactive widgets can never be clicked.
contextMenubooleannotruefalse opts the widget out of the right-click widget menu: in view mode right-clicks pass through it (to a widget beneath, or to the desktop's own menu), so a decorative widget is fully inert. Edit mode ignores the flag, so the widget stays configurable and removable there. Needs "minAppVersion": "0.2.3".
settingsarrayno[]Setting fields and preset names, mixed. The marketplace accepts up to 32 entries; a preset name counts as one. 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.
popoutobjectnononeDeclares the popout capability. See below.
fontsobjectnononeBundled 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"
]

The marketplace accepts up to 32 entries in settings; a preset name counts as one entry, whatever it expands to.

Field objects

KeyTypeRequiredMeaning
keystringyesThe name your code reads the value under.
typestringyesOne of the field types below.
labelstringyesShown in the settings dialog.
defaultanynoThe value before the user edits anything. Ignored for secretRef, filePath, and instances (see below).
optionsstring[]noThe choices for a select. A select with no default falls back to its first option.
min, maxnumbernoBounds for a number, and max caps the rows of a list. They are numbers, so date and time take no bounds.
placeholderstringnoHint text for empty text and filePath inputs, and for an empty list row. Needs "minAppVersion": "0.1.10".
pick"file" or "folder"noAdds a Browse button beside a filePath input and controls what it picks. Needs "minAppVersion": "0.1.10".
showIf{ "key", "equals" }noRenders the field only while the sibling setting named key has the value equals (its default counts while unset). Use primitive values; a key that names no sibling fails open and the field shows. Needs "minAppVersion": "0.2.6".

The date and time types need "minAppVersion": "0.3.4", and list needs "minAppVersion": "0.3.7". Older apps do not know them and quarantine the widget.

Field types

TypeThe user getsYour code gets
texta text inputstring
numbera number input (min/max bounds)number
booleana toggleboolean
selecta dropdown of optionsstring
colora color pickerstring
datea calendar pickera "YYYY-MM-DD" string, or "" while unset; dd.dates.fromDayKey makes it a local Date
timea clock pickeran "HH:MM" string, or "" while unset
listtext rows they add, reorder and removestring[], always an array; [] when they added nothing and the manifest declares no default
secretRefa picker over their stored secretsa name usable in dd.http.fetch secrets; never the value
filePatha path input (plus Browse with pick)nothing directly; used as settingKey in dd.files, dd.folders, dd.dialogs, dd.claudeUsage
instancesa picker over other widget instancesnothing 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:

PresetExpands toApplied by
panelStylea Panel style select (none / solid / transparent / image, default solid, key panel), plus from app 0.2.4 a Background image file picker (key panelImage) and an Image fit select (key panelImageFit), both shown in the settings dialog only while the style is image<dd-panel settings="panelStyle"> toggles its transparent / bare attributes and paints the image; zero widget code
accentColora 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
systraya Show the system tray toggle (key showTray, default on)<dd-tray-caret settings="systray"> shows or hides the caret; zero widget code
pinsa Show pinned apps toggle (key showPins, default on)<dd-apps settings="pins"> shows or hides the launchers (and never asks the host for pins while off); zero widget code. Bar widgets only, app 0.3.0+
weekStarta Week starts on select (monday / sunday, key weekStart); app 0.3.4+your code, via dd.dates.startOfWeek(d, settings.weekStart), which takes the string as stored
textScalea Text size select (tiny / small / medium / large / huge, key textSize); app 0.3.4+your code mirrors it onto the body (document.body.dataset.textScale = value); the base stylesheet turns it into the --dd-text-scale multiplier, which the autoscale skeleton and any calc(... * var(--dd-text-scale, 1)) of your own ride

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. Declare all of the preset's fields when you do, and drop the preset name from the manifest: expansion appends without deduplicating, so keeping both lists a field twice.

Published manifests carry presets already expanded (the store renders the field list from them), so a preset gaining a field reaches installed widgets only through a new widget version.

Bar

"kind": "bar",
"bar": {
  "thickness": 64, "reserve": 64, "edges": ["left", "right"], "allowOverlay": true,
  "autoHide": { "hideDelay": 150, "revealBand": 2, "slide": 140 }
}

A kind: "bar" manifest renders as the taskbar instead of a grid widget and must carry a bar block. thickness (logical px, 24 to 128) is the window's extent away from its edge, and the user may scale it (the Size slider in the bar's settings dialog, 50% to 200%), so treat it as the design size and lay out against the viewport; reserve (0 to thickness, default = thickness) is the strip the OS reserves, and a smaller value leaves transparent headroom for hover animations; edges (default ["bottom"]; "left" and "right" make a vertical bar) lists the edges the bar can dock to, the first being the default and several giving the user a Side choice; allowOverlay (default true) lets the user drop the reservation; autoHide (optional, every field optional) tunes the user's auto-hide: hideDelay ms before the bar hides (0 to 2000, default 150), revealBand logical px on the screen edge that reveals it (1 to 8, default 2), slide ms for the slide (0 to 600, default 140). Side, auto-hide and overlay are picked in the bar's own settings dialog. Bars need "minAppVersion": "0.3.0". The bar widgets reference covers the surface.

A bar declares no explicit filePath, secretRef or instances field: the bar's settings dialog never offers those types, so the user could never set one, and the loader, the upload validator and the CI check all refuse the manifest (app 0.3.7 or newer; older apps loaded it and never showed the field). Preset names are exempt: panelStyle keeps its image field on a bar, and its image option stays unavailable there (solid and transparent work).

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. A kind: "bar" widget declares it without max (its pane renders beside the strip, capped to the display).

KeyMeaning
maxCaps the pane size, in base grid cells like defaultSize. Each dimension 1..64.
entryThe 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, and from app 0.2.4 the pixel set pixelify-sans, press-start-2p, silkscreen; an unknown id quarantines the manifest, so the pixel ids also need "minAppVersion": "0.2.4") 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" }
}
KeyMeaning
bundleFamilies shipped in the widget folder. Each has an id (for self: references) and 1..6 files.
bundle[].files[].srcPath inside the widget folder, charset A-Za-z0-9._/-.
bundle[].files[].weightOne value or a variable range ("200 1000"), each within 1..1000. Default "400".
bundle[].files[].style"normal" (default) or "italic".
useOptional 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, --dd-font-pixelify-sans, --dd-font-press-start-2p, --dd-font-silkscreen), 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:

  1. minAppVersion, when present, must be a string that parses as x.y.z, and the running app must satisfy it. A too-old app reports "needs DeskDash x.y.z or newer".
  2. apiVersion newer than the host reports "requires a newer dashboard".
  3. The strict parse: unknown fields anywhere are rejected.
  4. id must match ^[a-z0-9][a-z0-9.-]*$ and equal the folder name.
  5. entry (and popout.entry) must be a relative path inside the widget folder: no leading /, no .., no \.
  6. Sizes (defaultSize, minSize, popout.max) must each be 1..64 cells per dimension; popout.max is required for grid widgets and absent on bars. bar.edges is a non-empty list of distinct edges; bar.autoHide fields must sit in their ranges (hideDelay 0 to 2000, revealBand 1 to 8, slide 0 to 600).
  7. A kind: "bar" manifest may not declare an explicit filePath, secretRef or instances field (app 0.3.7 or newer); preset names are exempt.
  8. 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.