A ScriptWidget project is a directory whose authoritative metadata lives in widget.json. Exported projects use the .swt extension, but the file is a ZIP archive so it can be inspected with standard tools.
My Widget/
├── widget.json
└── main.jsx
{
"entry": "main.jsx",
"formatVersion": 2,
"id": "com.example.my-widget",
"name": "My Widget",
"networkDomains": [],
"permissions": [],
"plugins": [],
"runtimeVersion": "1.0",
"supportedFamilies": ["systemSmall", "systemMedium", "systemLarge"],
"version": "1.0.0"
}The machine-readable contract is widget.schema.json. Unknown fields are rejected by the schema so typos do not silently change a package's security declaration.
formatVersion: package contract version; currently exactly2.id: stable reverse-DNS-style identifier, 3–128 characters.name: human-readable project name, 1–80 characters.version: semantic version such as1.2.0or1.2.0-beta.1.runtimeVersion: required ScriptWidget runtime API; currently exactly1.0.entry: a relative.jsor.jsxfile inside the package.supportedFamilies: one or more WidgetKit families. iOS, iPadOS, and macOS 27 addsystemExtraLargePortrait; packages may include it while retaining other families as fallbacks on older systems.actions: up to sixteen reusable script actions. Each action declares a stableid, title, SF SymbolsystemImage, and JavaScriptfunctionin the package entry file. The same action can be selected in Siri and Shortcuts, referenced by Widget<button actionID="…">/<toggle actionID="…">, and reused by a Control Widget.controls: up to eight declarative Control Center buttons or toggles. Each control declares a stableid, an SF SymbolsystemImage, and a JavaScriptactionID. The legacy directactionfunction remains readable for existing manifests, but it cannot be combined withactionID. Toggles also requirestateKeyand thestoragepermission; the new value is available to the action as$getenv("control-value").pushUpdates: optional self-hosted WidgetKit push registration. The object contains an HTTPSregistrationURLon a declarednetworkDomainshost and a non-secretchannel. ScriptWidget posts the WidgetKit token, package ID, and channel when the token changes; packages never receive the token in JSX.permissions: declared capabilities:network,location,health,storage, orfiles.networkDomains: lowercase host allowlist. It requiresnetwork; use exact hosts or a leading wildcard such as*.example.com.plugins: optional Data Source Plugin identifiers. Every data-source request also requiresnetworkand a matchingnetworkDomainsentry.- Optional discovery fields:
description,category,tags,icon,preview,author, andlicense.
The Mac Studio Config panel edits and validates these values before saving. New widgets get a manifest automatically. A legacy package containing main.jsx and optional meta.json is migrated when it is imported or exported.
Action calls receive a consistent environment on every system surface:
action-id: the declared action identifier;action-source:widget,control, orshortcut(including Siri calls);action-value:trueorfalsewhen a toggle supplies a value.
Control Widget calls additionally preserve control-id and control-value for
compatibility. Toggle state is stored under the package namespace before its
action runs; packages must declare the storage permission. A malformed
present manifest, missing action reference, unsafe state key, or undeclared
storage permission fails closed instead of invoking a legacy function.
ScriptWidget treats every imported .swt as untrusted. Before extraction it checks the ZIP central directory and rejects:
- absolute paths,
..traversal, backslashes, drive-style paths, invalid UTF-8, and excessive nesting; - symbolic links, encrypted entries, multi-disk archives, malformed ZIP64 metadata, and unsupported compression methods;
- archives over 32 MiB, more than 500 entries, a file over 25 MiB, or more than 64 MiB expanded;
- malformed central directories and suspicious zero-byte compression claims.
Extraction uses a unique temporary directory that is removed after every attempt. The extracted tree is checked again before copying. The archive filename does not control the installed project directory; the validated manifest name does.
Permission declarations are surfaced for review during import and enforced by the Package 2.0 runtime. Network calls require network, use HTTPS, and must match networkDomains. Legacy packages remain compatible but do not gain access to the new plugin API.
meta.json remains readable for bundled and existing projects. Package 2.0 makes widget.json authoritative when both exist. A package requiring an unknown format or runtime is rejected instead of being opened with undefined behavior.