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 no track (NepTunes.track is null), and no playerType when no player is running (NepTunes.playerType is null). playerState is then usually 0 (unknown), not 1. A player stopped at the end of its queue still sends the last track, with playerState 1 (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 the playerActivation permission.
  • 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 minNepTunesVersion to 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 artwork permission. There’s no new permission and no new consent on update. Without artwork the key does nothing, and widget-tools.mjs validate reports 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 minNepTunesVersion for it.
  • Changing the key rebuilds the widget’s window, so bump version as 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.authorPublicKey must equal bundle.sig’s authorPublicKey.
  • A manifest that declares authorPublicKey with no bundle.sig is 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"
      }
    ]
  }
}

← Quick startJavaScript API →

NepTunes on an older Mac

NepTunes 4 needs macOS 26 or later. If your Mac runs an earlier version, the App Store can still install the last NepTunes release that works on it.

  1. Open the App Store and click your name in the bottom-left corner.
  2. Find NepTunes in your apps and click the download button.
  3. When the App Store offers the last compatible version, click Download.

This only works if you’ve downloaded NepTunes before with the same Apple Account. Apple doesn’t offer older versions to new downloads. If that’s you, get in touch.