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/
panel.json
index.html
extras.html
app.js
app.css
assets/
bell.webp
inter.woff2
icon.svg
README.mdindex.htmlis the page the panel opens (any name, set bycontributes.panel.entry).extras.htmlis optional: the page Extras shows when its row is opened.README.mdis for whoever reads the package. Deska does not show it.
A complete panel.json
{
"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
| Field | Required | Rule |
|---|---|---|
schema | Yes | 1. A higher number is refused with “This package needs a newer Deska.” |
id | Yes | 3 to 40 lowercase letters, digits and dashes, starting and ending with a letter or digit. See The id. |
name | Yes | Up to 40 characters. |
version | Yes | Semver, like 1.2.0 or 1.2.0-beta.1, up to 64 characters. See Versions. |
description | Yes | One line, up to 140 characters. |
author | Yes | The name people see as the maker, up to 40 characters. |
icon | No | The path of an .svg, .png or .webp inside the package. The Library draws it on the panel’s card. |
color | No | #rrggbb, like #e66b4f. Stored in lowercase. |
category | No | productivity, media, dev, ambient or other, the default. |
permissions | No | A list of notify, open-url and clipboard-write. An unknown one refuses the package; a repeated one is kept once. |
network | No | Up to 20 host names the panel may reach over https. See Sites. |
embeds | No | Up to 10 host names whose pages the panel may show through Deska. Needs "sdk": 2. See Pages from other sites. |
signedInEmbeds | No | true 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. |
sdk | No | The 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. |
contributes | Yes | Where 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,com0tocom9andlpt0tolpt9. 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) ordeska-. - Deska and Dex (
deska,dex,ask-dex), the coding agents and their makers (claude,codex,cursor,opencode,antigravity,gemini,openai,anthropicand similar). - Built-in panels and their Library cards, like
terminal,browser,editor,todo,pomodoro,music,youtube,spotifyorsticky-note. - Deska’s own places and official sounding words:
extras,library,community,settings,official,sdk,api,admin, andapply, a page of deska.dev under/panels.
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.2comes before1.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.
| Permission | What it allows | People see |
|---|---|---|
notify | deska.notify: a notice in Deska, labelled with the panel’s name, at most one every ten seconds. | Send notifications |
open-url | deska.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-write | deska.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.comdoes not coverapi.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.
{
"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
embedsneeds"sdk": 2. embedsdoes not covernetwork: 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.htmlpage in the package.scope:workspace, the default, lets a project hold as many copies as the person adds.globalkeeps 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 numberswidthandheightfrom 120 to 4000.
contributes.extras
Optional: a row in the Extras popover.
title: the row’s name, up to 40 characters.view: optional, an.htmlpage 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,mdandtxt. Anything else refuses the package, includingwasm,mp3andttf. - 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 likecon.htmlorcom1.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_Storeor.gitare 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.
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:
| Message | Fix |
|---|---|
| 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. |