Skip to main content
Every addon (a plugin, theme, or icon pack) ships a manifest: a block of metadata that describes it. They all use the same shape, validated by the same manager before the addon loads. This page is the single source of truth for that shape; each addon kind documents only what’s specific to it.
Fonts are the exception. A font is a bare file dropped in the Fonts folder, with no manifest and no folder of its own. See Fonts below.

Required fields

These must be present and defined, or the addon is rejected when it loads.
string
required
A unique identifier for the addon. Used as its key in every manager operation and as its storage namespace, so keep it stable across versions.
string
required
The human-readable name shown in settings.
string
required
A short summary of what the addon does.
AddonAuthor[]
required
A non-empty array of authors. An empty array fails validation just like a missing field.
string
required
The addon’s version. Bump it on every release so clients can detect updates.
string
required
The entry point the bundle is built from. Themes ignore this field; see Themes below.

Optional fields

Used for display and updates. Leaving them out is fine.
string | { uri: string }
The addon’s icon: an asset name, or an object with a uri for a remote image.
string
A URL Unbound checks to discover newer versions.
string
Where the addon’s bundle is hosted.

Example

manifest.json
The manifest is validated the moment the addon loads. Any required field that’s missing or undefined rejects it, and authors must be a non-empty array.
The id is the field you reference everywhere else: manager calls like plugins.toggle('my-plugin'), your storage store name, and the addon’s entry in the enabled-state map. Pick it once and never change it.

Per-addon details

The shape is shared, but the addon kinds diverge in what they need on disk and which fields actually do anything. The loader scans Plugins, Themes, and Fonts at startup, while icon packs are installed by the client under Unbound/Icons. See iOS for where these live on device.
A plugin is a directory inside Plugins containing a manifest.json. Entries that aren’t directories, or that have no manifest, are skipped.main is required and must resolve to a real file. Resolution works one of two ways:
  • A value starting with / is treated as an absolute path and used as-is.
  • Anything else resolves relative to the plugin’s own folder.
Either way the resolved path must exist and must not be a directory, otherwise the plugin is skipped. Unbound reads that file and evaluates it into your plugin’s running instance. See Creating an Addon.
A theme is a directory containing both a manifest.json and a bundle.json. The bundle file name is fixed: the loader looks for exactly bundle.json next to the manifest, and skips the theme if it isn’t there or isn’t a JSON object.
Themes ignore main entirely. The field is still required by manifest validation, but the loader never reads it when locating a theme’s bundle. Pointing it at a differently named file does nothing; rename the file to bundle.json instead.
Besides identifying the theme, its id is the value stored as applied while it’s the active theme. Only one theme is applied at a time, so this is how Unbound remembers which. The palette and background live in bundle.json, not the manifest. See Theme Types.
An icon pack is a folder of image files mirroring Discord’s asset tree, installed under Unbound/Icons/<id>. It uses the shared manifest above, plus two fields of its own:
'icon-pack'
Tags the manifest as an icon pack. When present on install it must match, otherwise the install is rejected.
'github' | 'other'
How the pack’s files are fetched. If omitted, it’s inferred from main: a github.com URL means github, anything else means other.
For a github pack, main is the repo URL (optionally with a /tree/<branch>/<path> suffix) and Unbound downloads that tree’s images into the pack folder. For other, the pack installs through the shared bundle path like any other addon. See Icon Packs.
A font has no manifest and no folder. It’s a single font file dropped directly into the Fonts directory, and the loader registers it by reading the font’s own internal full name, not its file name.Registering a font does not apply it. Fonts only take effect through the unbound.font-states setting, which maps a font family name to the name of the font to use instead. See Settings for that key.
The key "*" in font-states is a wildcard that overrides every font request in the app, ignoring whatever family was asked for. If a wildcard entry is set, per-family entries never take effect.