Docs menu

Plugin Development

Manifest Reference

Every manifest.json field, the settings widget matrix, and URL pattern rules.

manifest.json sits at the root of the plugin folder. All keys are camelCase. Unknown keys are rejected — a typo fails the whole manifest rather than being silently ignored.

Top-level fields

FieldRequiredTypeRules
identityyesstring^[a-z0-9_-]+@[a-z0-9_-]+$ — lowercase name, @, author. Dots are forbidden. Permanent ID: settings, storage and the install folder are all keyed by it.
nameyesstringDisplay name, must be non-empty.
versionyesstringMAJOR.MINOR.PATCH, three integers.
descriptionnostringShown in the plugin list.
homepagenostringProject/author URL.
iconnostringPath relative to the plugin folder. No .., no absolute paths, no drive letters, no leading / or \.
minAppVersionnostringThree-part version. If the running FluxDown is older, the plugin is skipped at load (logged, not an error).
resolversnoarrayAt most one entry in v1. See below.
hooksnoobjectSee below.
permissionsnoarrayCapability grants. v1 accepts "ffmpeg" and "ytdlp". Unknown values are rejected.
settingsnoarrayDeclarative settings fields. See below.

A plugin may declare a resolver, hooks, or both. A manifest with neither is valid but does nothing.

resolvers[0]

{
  "resolvers": [
    {
      "match": { "urls": ["*://host.com/share/*", "*://cdn.host.com/*"] },
      "entry": "resolver.js",
      "timeoutMs": 15000
    }
  ]
}
FieldRequiredRules
match.urlsyesNon-empty list of URL patterns (see pattern rules below).
entryyesScript file, safe relative path. Must define globalThis.resolve.
timeoutMsnoPer-call timeout in milliseconds. Must not be 0. Replaces the 10 s default but is capped at the 30 s hard ceiling regardless of what you write.

hooks

{
  "hooks": {
    "entry": "hooks.js",
    "events": ["onStart", "onDone", "onError"],
    "match": { "urls": ["*://host.com/*"] }
  }
}
FieldRequiredRules
entryyesScript file, safe relative path. Must define a global function per subscribed event.
eventsyesNon-empty subset of onStart, onError, onDone, onMetaProbed. Anything else is rejected.
matchnoOptional URL filter, same pattern rules. If present, urls must be non-empty. Omitted = the hooks fire for every task.

Caveat: if the same plugin also declares a resolver, onMetaProbed never fires for its tasks — resolver tasks skip the metadata probe entirely. FluxDown logs a warning if you subscribe to it anyway.

permissions

Extra host capabilities a plugin opts into. Empty or omitted = the base sandbox (network via flux.fetch, flux.storage, logging). v1 recognises two values:

ValueGrants
ffmpegThe flux.ffmpeg API — run the resolved ffmpeg on a finished file (see the API reference).
ytdlpThe flux.ytdlp API — run the resolved yt-dlp from resolve or any hook (see the API reference).
{ "permissions": ["ffmpeg", "ytdlp"] }

Unknown values fail the whole manifest, so an older FluxDown that doesn’t know a permission rejects the plugin rather than silently ignoring it — pair a new permission with a minAppVersion bump.

settings[]

Each entry describes one field; the app generates the form. Keys must be unique and non-empty.

{
  "settings": [
    { "key": "apiToken", "title": "API token", "type": "string", "widget": "password", "required": true },
    { "key": "quality",  "title": "Preferred quality", "type": "string", "widget": "select",
      "options": [ { "value": "hd", "label": "HD" }, { "value": "sd", "label": "SD" } ],
      "default": "hd" },
    { "key": "maxRetries", "title": "Max retries", "type": "number", "min": 0, "max": 10, "default": "3" },
    { "key": "verbose", "title": "Verbose logging", "type": "boolean", "default": "false" }
  ]
}
FieldRequiredNotes
keyyesUnique within the plugin. Read back as flux.settings.<key>.
titleyesForm label.
descriptionnoHelp text below the field.
typeyesstring, number, or boolean.
widgetnoDefaults by type: string→text, number→number, boolean→toggle.
optionsselect onlyNon-empty {value, label} list.
defaultnoAlways a string, even for numbers ("3") and booleans ("true"/"false"). For select, must be one of the option values; for number, must parse and fall inside min/max.
requirednoThe form refuses to save an empty value.
min / maxnumber onlyFinite, inclusive bounds, min ≤ max.
patternstring onlyA JavaScript RegExp (not Rust regex) validated against the value on save.
helperScriptstring onlyA snippet the host shows a Copy button for, next to the field. The host only copies the text to the clipboard — it never runs it. The user pastes it into the target site’s browser DevTools console (typical use: read document.cookie and copy a login cookie back into the field). Non-empty, ≤ 4 KB.
helperLabelwith helperScriptButton label (≤ 60 chars). Only valid together with helperScript; defaults to a generic “Copy helper script” when omitted.

Widget × type matrix

Only these combinations are valid — anything else fails validation:

WidgetAllowed type
text, password, textarea, folder, selectstring
toggleboolean
numbernumber

URL pattern rules

Patterns are not regular expressions and not browser match patterns. The rules, in full:

  • * is the only wildcard and matches any run of characters (including / and :).
  • Text between *s must appear in order as substrings.
  • If the pattern doesn’t start with *, the first segment must be a prefix of the URL.
  • If it doesn’t end with *, the last segment must be a suffix.
  • Matching is case-insensitive (both sides are lowercased whole, path included).
  • A bare * matches everything.

Examples:

Patternhttps://www.youtube.com/watch?v=abc
*://www.youtube.com/watch*matches
https://x.com/a (no wildcard)exact match only
*://x.com/* vs https://y.com/ano match — ://x.com/ not found
youtube.com/*no match — pattern is prefix-anchored, URL starts with https://

Validation summary

The manifest is validated when the plugin is installed and every time it’s loaded. On failure the plugin is skipped and the reason lands in the log. The checks, in order: identity format → name non-empty → version format → minAppVersion format → icon path safety → at most one resolver → resolver entry path / non-empty match.urls / timeoutMs ≠ 0 → hooks entry path / non-empty valid events / non-empty match.urls if present → settings key uniqueness and the per-field rules above → permissions are all recognised values.

Both entry scripts are also compile-checked at install time — a syntax error is rejected up front rather than discovered on the first download.

Something wrong with this page?