Skip to content

Build panels

panel.json reference

Every field of panel.json, the rules each one is checked against, the ids and names that are refused, and the limits of a package.

The package

A package is a folder, or once published a zip, with panel.json at its root. It carries the panel’s whole behavior as web files and nothing that runs on the computer. Only panel.json and the pages it names are required; the rest is whatever your pages load.

pomodoro-plus/
pomodoro-plus/
  panel.json
  index.html
  extras.html
  app.js
  app.css
  assets/
    bell.webp
    inter.woff2
  icon.svg
  README.md
  • index.html is the page the panel opens (any name, set by contributes.panel.entry).
  • extras.html is optional: the page Extras shows when its row is opened.
  • README.md is for whoever reads the package. Deska does not show it.

A complete panel.json

panel.jsonjson
{
  "schema": 1,
  "id": "pomodoro-plus",
  "name": "Pomodoro Plus",
  "version": "1.2.0",
  "description": "A focus timer with breaks and a daily count.",
  "author": "pasho",
  "icon": "icon.svg",
  "color": "#e66b4f",
  "category": "productivity",
  "permissions": ["notify"],
  "network": ["api.example.com"],
  "sdk": 1,
  "contributes": {
    "panel": { "entry": "index.html", "scope": "workspace", "size": { "width": 360, "height": 280 } },
    "extras": { "title": "Pomodoro Plus", "view": "extras.html" }
  }
}

Deska reads every field when you load the folder, and deska.dev reads them all again when you publish. Every problem is reported at once, so you can fix a manifest in one pass.

Fields

FieldRequiredRule
schemaYes1. A higher number is refused with “This package needs a newer Deska.”
idYes3 to 40 lowercase letters, digits and dashes, starting and ending with a letter or digit. See The id.
nameYesUp to 40 characters.
versionYesSemver, like 1.2.0 or 1.2.0-beta.1, up to 64 characters. See Versions.
descriptionYesOne line, up to 140 characters.
authorYesThe name people see as the maker, up to 40 characters.
iconNoThe path of an .svg, .png or .webp inside the package. The Library draws it on the panel’s card.
colorNo#rrggbb, like #e66b4f. Stored in lowercase.
categoryNoproductivity, media, dev, ambient or other, the default.
permissionsNoA list of notify, open-url and clipboard-write. An unknown one refuses the package; a repeated one is kept once.
networkNoUp to 20 host names the panel may reach over https. See Sites.
embedsNoUp to 10 host names whose pages the panel may show through Deska. Needs "sdk": 2. See Pages from other sites.
signedInEmbedsNotrue asks that the pages under embeds keep the person’s sign-in to those sites, in a session of the panel’s own. Needs embeds. See Staying signed in.
sdkNoThe SDK version the panel was written for: 1, the default when it is absent, or 2 for embeds. A higher one than this Deska speaks is refused like schema.
contributesYesWhere the panel appears, with at least panel. See Places.

Any other top-level field refuses the package, so a typo like permission never passes silently. Text fields are trimmed, and name, description, author and the Extras title may not hold control, zero-width or bidi characters, or half of a character whose other half is missing. Whole emoji are fine.

The id

The id names the panel everywhere: the folder it unpacks to on every computer, its storage, its listing and its page on deska.dev. The first published version claims it for your account, and only that account can publish more versions under it.

Deska refuses an id that is not usable on every system it runs on:

  • A name Windows keeps for a device: con, prn, aux, nul, com0 to com9 and lpt0 to lpt9.
  • constructor.

deska.dev also refuses, when you publish, the ids that would read as Deska’s own. A loaded folder with one of these still runs while you build:

  • Any id that starts with dev- (Deska’s own name for loaded folders) or deska-.
  • Deska and Dex (deska, dex, ask-dex), the coding agents and their makers (claude, codex, cursor, opencode, antigravity, gemini, openai, anthropic and similar).
  • Built-in panels and their Library cards, like terminal, browser, editor, todo, pomodoro, music, youtube, spotify or sticky-note.
  • Deska’s own places and official sounding words: extras, library, community, settings, official, sdk, api, admin, and apply, a page of deska.dev under /panels.
TipPick a descriptive id with two words, like focus-timer or team-clocks. It shows in the panel’s address on deska.dev and never changes once published.

Names that read as Deska

Every community panel says it was made by someone other than Deska, so its name, author and Extras title cannot pass for Deska’s own. When you publish, deska.dev refuses one that holds the word “Dex”, or a word that starts with “Deska”, and names each field to change.

  • Words are read the way a person reads them: in any case, with accents, lookalike letters from other alphabets, full-width or styled letters, digits standing for letters (3, 4 and 5 for e, a and s) and letters spaced one by one.
  • A part of a camelCase word counts: “DexTimer” and “MyDeska” are refused, “DeskApp” passes.
  • Dex never counts at the end of a word, so MangaDex, Rolodex, Dexter and Codex pass.
  • An author may be called Dex, as long as Dex is not the whole name.

Versions

version is semver: three whole numbers without leading zeros, and an optional prerelease after a dash (1.4.0, 2.0.0-rc.1). Build metadata after a plus is not accepted.

  • A published version never changes. To change anything, raise the version.
  • Each new version must be greater than the last. Numbers compare as numbers at any length, and a prerelease ranks below its release: 1.2.0-beta.2 comes before 1.2.0.
  • While you build, the version is not checked against anything: a loaded folder has no published version.

Permissions

Without any permission a panel can draw, keep its own data, follow the theme, know when it is visible, set its title and, if it declares one, fill its Extras row. Ask only for what you use: people read the list before they install.

PermissionWhat it allowsPeople see
notifydeska.notify: a notice in Deska, labelled with the panel’s name, at most one every ten seconds.Send notifications
open-urldeska.openUrl: an https link in the browser, from a click in the panel. Deska asks the person the first time for each site.Open links
clipboard-writedeska.clipboard.write and deska.clipboard.writeImage: text, or a PNG or JPEG image, to the clipboard, from a click in the panel. Without it, the page’s own copy commands do nothing too.Copy to your clipboard

Each version asks for its own permissions. People who installed an earlier one are asked again before an update that wants more. There is no permission to read the clipboard, files, the microphone, the camera or the location. Keeping files with deska.files and saving one with deska.files.save need none: the files are the panel’s own, and the save dialog is the person’s consent.

Sites

network lists the hosts the panel may reach, and it can reach those and no others: fetch, images, fonts, audio and video all come from the package or these hosts, over https. A panel with an empty list cannot reach the internet at all.

  • A plain lowercase host name with at least one dot: api.example.com. No scheme, path, port, wildcard or IP address.
  • Exact names only. example.com does not cover api.example.com; list each host. Where a service spreads over many hosts, list the ones it uses, like Net Radio does with six stream mirrors.
  • At most 20. A repeated host is kept once.

People see every host on the panel’s page. The question before installing names them too, or the first five and how many more when there are seven or more. An update that adds a host names each new one.

Pages from other sites

embeds lists the hosts whose pages the panel may show, like a video player or a map. The panel never frames them itself: it asks Deska with deska.embed, and Deska draws its own frame over the panel. See the sandbox for what that frame allows.

panel.jsonjson
{
  "embeds": ["www.youtube-nocookie.com"],
  "sdk": 2
}
  • The same host rules as network: a plain lowercase host name, exact, with no scheme, path, port, wildcard or IP address.
  • At most 10. A repeated host is kept once.
  • Never Deska’s own sites, or a sign-in or payment host, with every host under them: deska.dev, clerk.com, accounts.google.com, appleid.apple.com, login.microsoftonline.com, github.com, gitlab.com, stripe.com, paypal.com and a few more. A framed copy of a sign-in page is how passwords get stolen.
  • A package that declares embeds needs "sdk": 2.
  • embeds does not cover network: list a host in both when the panel also fetches from it.

People see these hosts on the panel’s page, and the question before installing says the panel will show pages from them. An update that adds a host asks again. Publishing a version with embeds needs an account Deska approved to publish.

Installed from the Library, a version shows these pages only once Deska reviewed and approved that exact version. Each new version waits for its own approval, and Deska can take an approval back. A folder you load in Build a panel shows them while you build.

Staying signed in

Pages from other sites run signed out by default. A panel that needs the person’s own view of a site, like their calendar, sets "signedInEmbeds": true: its pages then keep the person’s sign-in to those sites, in a session of this panel’s own, apart from the app, the browser panels and every other panel. It needs hosts under embeds, and it takes effect only once Deska verified that exact version for it; until then, or if Deska approves it signed out, the pages show signed out. The panel’s page says it asks.

Deska can also block a site for every panel at once. A computer stops reaching and showing it at its next check, whatever panel.json says, and a version that names it under network or embeds cannot be published.

Places

contributes.panel

Required: the panel on the desk.

  • entry: the path of an .html page in the package.
  • scope: workspace, the default, lets a project hold as many copies as the person adds. global keeps one per project: adding it again brings the open one forward. Use it for anything that should never run twice, like a radio.
  • size: optional, the size a new panel opens at, as whole numbers width and height from 120 to 4000.

contributes.extras

Optional: a row in the Extras popover.

  • title: the row’s name, up to 40 characters.
  • view: optional, an .html page that opens inside the popover. Without it the popover shows the panel’s entry.

Unknown keys under contributes are ignored with a warning, so a place a newer Deska learns never locks an older one out of your panel. See Extras for what the row shows.

Package limits

  • A package is at most 5 MB zipped, and unpacks to at most 20 MB and 500 files.
  • File types: html, js, mjs, css, json, svg, png, jpg, jpeg, webp, gif, woff2, md and txt. Anything else refuses the package, including wasm, mp3 and ttf.
  • Paths are relative and at most 200 characters, with no .., no empty or . segment, no backslash and nothing under __deska/, where the SDK lives.
  • Every name must be one Windows can write: none of : * ? " < > |, no name ending in a dot or a space, no device name like con.html or com1.js, and no two paths that differ only in letter case.
  • Plain files only: symbolic links and special files are refused.
  • __MACOSX/ and dotfiles like .DS_Store or .git are skipped, not refused. A zip made of the folder itself (pomodoro-plus/panel.json) is read as if made from inside it.
  • Every page and the icon panel.json names must be in the package.
TipAudio and video do not fit the file types on purpose: stream them from a host you declare (Net Radio, Window View) or generate sound in the page (Noise Machine).

What the checks say

Deska lists every problem on the loaded folder’s page in Build a panel, the first fifty and how many more. The messages you are most likely to meet:

MessageFix
Unknown field "…".Remove or rename the field; only the fields above are read.
"id" is 3 to 40 lowercase letters, digits and dashes…Lowercase it and replace spaces or underscores with dashes.
"id" cannot be "…". Pick another.The id is a device name or constructor.
"version" must be a semver version such as 1.0.0.Use three numbers: 0.1.0.
"…" is N characters; the limit is M.Shorten the name, description, author or Extras title.
"…" holds invisible or control characters.Retype the text; a pasted name can carry a zero-width space.
"…" is not a host name (no scheme, port, wildcard or IP address).Write api.example.com, not https://api.example.com/v1.
Unknown permission "…". Known: notify, open-url, clipboard-write.Check the spelling.
"contributes.panel.entry" must point at an .html page.Point it at a page, like index.html.
… is named in panel.json but is not in the package.Add the file or fix the path; paths are case sensitive.
…: this kind of file is not allowed in a package.Remove it, or convert it to an allowed type.
A package holds at most 500 files; this one has N.Load the folder panel.json is in, not a project root.
… cannot be shown inside a panel: it is a sign-in, payment or Deska page.Remove the host from embeds; it is never allowed.
A package with "embeds" needs "sdk": 2 or later.Add "sdk": 2 to panel.json.
"signedInEmbeds" needs hosts under "embeds".List the sites under embeds, or remove signedInEmbeds.
Deska blocked … for every panel, so a package may not reach it or show its pages.Remove the host from network and embeds.
This package needs a newer Deska.Set schema to 1 and sdk to 1, or 2 with embeds, or update Deska.