manifest.json reference
Every widget has a manifest.json at the root of its .nepget bundle. It tells
NepTunes how to load and present the widget.
Required fields
| Field | Type | Description |
|---|---|---|
manifestVersion |
Int | Manifest format version. Always 1. |
id |
String | Unique identifier in reverse-domain notation, e.g. com.example.my-widget. |
name |
String | Display name shown in the widget picker. |
version |
String | Semantic version, e.g. 1.0.0. |
entry |
String | Entry HTML file, typically index.html. |
defaultSize |
Object | Default window size: { "width": Int, "height": Int } in pixels. |
Optional fields
| Field | Type | Description |
|---|---|---|
author |
String | Author name. |
description |
String | Short description of the widget. |
license |
String | License identifier, e.g. MIT. |
homepage |
String | Homepage URL. |
minSize |
Object | Minimum window size { width, height }. Applies to user resizing and to NepTunes.setSize() alike, whatever resizable says. |
maxSize |
Object | Maximum window size { width, height }. Applies to the same paths. Omitting it is not “unbounded”, a widget window is never larger than the display it is on. |
resizable |
Bool | Whether the user can resize the widget, by dragging its edges, or a resize handle the widget draws itself. Defaults to true; set it to false for a fixed-size widget. |
pointerTracking |
Bool | Report the pointer as pointermove / pointerleave events. Defaults to false. NepTunes 4.1+; see Pointer tracking. |
alwaysVisible |
Bool | Keep the widget on screen when nothing is playing, unless the user changes its Show setting. Defaults to false. NepTunes 4.1+; see Showing without playback. |
supportsNoPlayback |
Bool | The widget draws its own “nothing playing” state, so the user may set its Show setting to Always. It still follows playback until they do. Defaults to false. NepTunes 4.1+; see Showing without playback. |
supportsMotionArtwork |
Bool | The widget can play Apple Music motion (animated) artwork over its cover. Counts only with the artwork permission. Defaults to false. NepTunes 4.1+; see Motion artwork. |
preview |
String | Preview image filename inside the bundle. |
icon |
String | Icon filename inside the bundle. |
permissions |
Array | The permissions the widget needs. |
settings |
Object | A { "schema": [ … ] } object describing user settings. |
minNepTunesVersion |
String | Minimum NepTunes version, major.minor.patch, e.g. 4.2.0. If it’s newer than the running app, NepTunes refuses to install the widget and says which version it needs, flags an already-installed copy in Settings, and shows a gallery update as blocked instead of installing it. Set it whenever your widget uses a permission, setting type or API added in a later NepTunes: an older app that meets a value it doesn’t know can’t tell the user which version to get. A value NepTunes can’t parse counts as too new. |
authorPublicKey |
String | Base64 of your Ed25519 signing key’s public half, 32 raw bytes, no ed25519: prefix. Required when the bundle ships a bundle.sig, and it must equal that file’s authorPublicKey; declaring it without a signature makes the bundle refuse to install. Pinned on first install; a change forces users to re-consent. See Updates & signing. |
releaseNotes |
String | What changed in this version. Shown in the update confirmation sheet. |
Showing without playback
A widget is on screen only while a track is playing or paused. When playback stops (including at the end of the queue, when the player still reports the last track), or the music app quits, NepTunes orders it out, and it comes back with the next track. For a widget about the current track that’s usually what you want. For one that isn’t (a chart, a scrobble count, a calendar of your listening) it isn’t, so the manifest can say the widget copes with nothing playing. Either of two keys does it:
"alwaysVisible": true
for a widget whose content doesn’t depend on the current track. It stays on screen with nothing playing, even with no music app running.
"supportsNoPlayback": true
for a now-playing widget with a good empty state. It keeps following playback, but the user can choose to keep it up.
Either key gives the widget a Show setting in NepTunes: While music is playing or Always.
With alwaysVisible it starts at Always, with supportsNoPlayback at While music is playing, and
the user can change it either way. A widget with neither key gets no Show setting and follows
playback.
- NepTunes still has to be running, with desktop widgets turned on and the widget active, and widgets still need NepTunes Pro. Show lifts only the playback rule.
- Other widgets keep following playback. Only yours stays up.
- NepTunes draws no placeholder for you. With nothing playing your widget still gets
statechange, with notrack(NepTunes.trackisnull), and noplayerTypewhen no player is running (NepTunes.playerTypeisnull).playerStateis then usually0(unknown), not1. A player stopped at the end of its queue still sends the lasttrack, withplayerState1(stopped). Show your own empty state rather than waiting for a track. - Clicking the empty state should call
NepTunes.activatePlayer(). It brings the current player forward or, when none is running, launches the preferred player if the user set one in Settings, otherwise the one they last listened in. It needs theplayerActivationpermission. - NepTunes before 4.1 ignores both keys, as it does any key it doesn’t know, and hides the widget
with playback as before. You don’t need to raise
minNepTunesVersionto use them.
Motion artwork
Many Apple Music albums have motion artwork: a short, silent video loop of the cover. A widget that shows the cover can ask for it:
"supportsMotionArtwork": true,
"permissions": ["artwork"]
- It rides on the
artworkpermission. There’s no new permission and no new consent on update. Withoutartworkthe key does nothing, andwidget-tools.mjs validatereports the pair. - The widget’s settings in NepTunes gain an Animated cover switch, on by default. When the user turns it off, your widget gets no loop and NepTunes downloads nothing for it.
- NepTunes before 4.1 ignores the key and your widget shows static artwork, so you don’t need
minNepTunesVersionfor it. - Changing the key rebuilds the widget’s window, so bump
versionas for any manifest change.
The loop reaches your widget as track.motionArtworkURL; see
Motion artwork in the JavaScript API for when it’s there
and how to play it.
bundle.sig, the manifest’s signature sidecar
A signed bundle carries a second file next to manifest.json: bundle.sig, holding
the author’s Ed25519 signature over the SHA-256 hash of every file in the bundle ,
manifest.json included. You never hand-write it; embed-sign generates it
(widget-tools.mjs ,
Updates & signing explains how to get it and where keygen
puts your keys):
node widget-tools.mjs embed-sign MyWidget.nepget \
--key .keys/my-widget-author.pem
node widget-tools.mjs embed-verify MyWidget.nepget
Two rules tie the two files together, and NepTunes enforces both at install time:
manifest.authorPublicKeymust equalbundle.sig’sauthorPublicKey.- A manifest that declares
authorPublicKeywith nobundle.sigis rejected , claiming an identity without proving it is worse than claiming none.
Because the manifest is itself covered by the signature, any manifest edit (a version
bump, a new permission) means re-running embed-sign. The full format, the exact bytes the
signature covers, and the verification order are in
Updates & signing.
Complete example
This is the manifest from the “Now Playing” widget in the gallery:
{
"manifestVersion": 1,
"id": "pl.micropixels.neptunes.widget.now-playing",
"name": "Now Playing",
"version": "1.9.0",
"author": "NepTunes",
"description": "Displays the currently playing track with playback controls",
"license": "MIT",
"homepage": "https://neptunesmac.app",
"entry": "index.html",
"defaultSize": { "width": 368, "height": 220 },
"minSize": { "width": 328, "height": 200 },
"resizable": false,
"preview": "preview.jpg",
"permissions": ["artwork", "playbackControl"],
"settings": {
"schema": [
{
"id": "showArtwork",
"type": "checkbox",
"label": "Show Album Artwork",
"default": true
},
{
"id": "theme",
"type": "radio",
"label": "Theme",
"options": [
{ "value": "dark", "label": "Dark" },
{ "value": "light", "label": "Light" }
],
"default": "dark"
}
]
}
}