Plugin Manifest
The full plugin.json field reference enforced by Aviato.
plugin.json is the single source of truth Aviato uses to discover,
validate, and configure a plugin. Aviato parses it on startup,
validates it against a strict Zod schema, and refuses to start any
plugin whose manifest fails validation. Errors surface in the admin
UI as Zod issue paths, so the failing field is always obvious.
Identity
| Field | Type | Required | Notes |
|---|---|---|---|
id | string | yes | Lowercase kebab-case (/^[a-z0-9-]+$/). Must be unique across all installed plugins. By convention, plugins authored by the Aviato team are prefixed aviato-. |
name | string | yes | Display name shown in the admin UI. |
version | string | yes | Semver (/^\d+\.\d+\.\d+/). Bump on breaking manifest or capability changes. |
description | string | yes | One-sentence summary, surfaced in the plugin list and the Add Library wizard. |
author | string | yes | Author or organization name. |
license | string | yes | SPDX-style identifier (MIT, Apache-2.0, etc.). |
repository | string (URL) | no | Source repository link. |
homepage | string (URL) | no | Project homepage. |
Runtime
| Field | Type | Required | Notes |
|---|---|---|---|
engine | 'bun' | 'node' | 'python' | 'binary' | yes | Process runtime. Aviato spawns the plugin with the matching launcher. binary runs the file at entry directly. |
entry | string | yes | Path to the entry script (or binary), relative to the plugin directory. Typically src/index.ts for bun plugins. |
aviato.minVersion | string | yes | Minimum Aviato server version. Aviato refuses to load a plugin whose minVersion is newer than the running server. |
dependencies | string[] | no | IDs of other plugins this plugin requires. Aviato refuses to start the plugin if any dependency is missing or disabled. |
Declared work
capabilities and subscriptions together describe everything a
plugin can do. A plugin with neither does nothing.
capabilities
"capabilities": ["filesystem", "indexer", "library"]string[], required, may be empty. Allowed values:
| Value | Adds |
|---|---|
filesystem | filesystem.* RPC handlers. See filesystem.mdx. |
indexer | indexer.* RPC handlers. See indexer.mdx. |
library | library.* RPC handlers and a library type. See library.mdx. |
Each capability declared here must have a matching
capabilityConfig.<name> block when that capability defines one.
A plugin with capabilities: [] is a pure subscriber: it does
no RPC work, only reacts to hooks, events, and views. The bundled
embedded-metadata, external-metadata, external-subtitles,
posters, and thumbnails plugins all work this way.
mediaTypes
Required for library and indexer capabilities. Aviato uses it
to short-list which plugins to consider for a given library type.
Two equivalent forms:
// Simple list
"mediaTypes": ["movies", "tv"]// Per-type config flags
"mediaTypes": {
"tv": { "episodic": true },
"movies": {}
}Currently understood per-type flags:
| Flag | Default | Effect |
|---|---|---|
episodic | false | Enables player "End of current episode" sleep timer (and future auto-advance). |
seekable | true | When false, hides player seek controls. |
idleProtection | true | When false, suppresses the "Are you still there?" prompt. |
Unrecognized fields are accepted to keep the schema forward-compatible: a plugin built against a newer SDK can still load on an older Aviato server.
capabilityConfig
Per-capability static configuration. Keys must match declared capabilities.
"capabilityConfig": {
"filesystem": { "supportsWatch": true, "supportsLocalFileAccess": true, "supportsWrite": true },
"library": { "bundling": { "strategy": "per-folder" }, "paths": [...] }
}filesystem block:
| Field | Type | Notes |
|---|---|---|
supportsWatch | boolean | Plugin implements filesystem.watch. |
supportsLocalFileAccess | boolean | Files exposed by this plugin can be opened with a local path (via filesystem.getLocalPath). |
supportsWrite | boolean | Plugin can persist user-uploaded files. |
library block: see library.mdx for the full
breakdown.
indexer has no static config; its declared mediaTypes and
runtime behavior are enough.
subscriptions
Reactive bindings to events, hooks, and views.
"subscriptions": {
"events": ["library.item.updated", "library.item.removed"],
"hooks": [
{ "name": "pipeline.probe.afterProcess", "order": 40, "requiresLocalFile": true }
],
"views": ["ui.item.detail.actions"]
}Names match ^[a-z0-9*]+(\.[a-z0-9*]+)*$. * is a single-segment
wildcard: library.item.* matches library.item.added but not
library.item.added.bulk. Hook entries are objects with the following
fields:
| Field | Type | Notes |
|---|---|---|
name | string | Hook name. Required. |
order | int 0–100 | Execution order within the chain. Default 50. |
requiresLocalFile | boolean | The handler needs the bundle's primary file readable from a real path on disk. Default false. See hooks.mdx for orchestrator behavior. Planned — schema and registry persist the flag; ingestion-pipeline materialization is not yet implemented. |
Full catalog: hooks.mdx.
User configuration
configuration[] declares user-editable settings rendered as a
form in the admin UI.
"configuration": [
{ "key": "tmdbApiKey", "label": "TMDb API Key", "input": "text", "required": false },
{ "key": "language", "label": "Metadata language", "input": "text", "default": "en-US" }
]See configuration.mdx for the full input vocabulary and how saved values reach the plugin process.
Rate limiting
rateLimit lets a plugin cooperate with upstream API quotas.
Aviato wraps every inbound RPC call to the plugin in a per-plugin
limiter.
"rateLimit": {
"maxConcurrency": 4,
"requests": [
{ "max": 40, "window": "10s" }
]
}| Field | Type | Notes |
|---|---|---|
maxConcurrency | positive int | Max in-flight RPCs at once. Defaults to Infinity. |
requests[] | { max, window }[] | Sliding-window quotas. window is <n>[smh], for example "10s", "1m", "1h". Multiple entries AND together. |
See the rate-limit section of indexer.mdx for guidance on choosing values.
Worked examples
Library plugin (movies)
{
"id": "aviato-library-movies",
"name": "Movies Library",
"version": "1.0.0",
"description": "Movie library with rich metadata, filtering, and sorting",
"author": "Aviato",
"license": "MIT",
"engine": "bun",
"entry": "src/index.ts",
"aviato": { "minVersion": "0.1.0" },
"capabilities": ["library"],
"mediaTypes": ["movies"],
"capabilityConfig": {
"library": {
"bundling": { "strategy": "per-folder" },
"paths": [
{ "extensions": ["system:video"] },
{ "type": "trailer", "rules": ["*trailer*", "trailers/**/*"], "extensions": ["system:video"] },
{ "type": "extra", "rules": ["extras/**/*", "featurettes/**/*"], "extensions": ["system:video"] },
{ "type": "companion", "extensions": ["system:text", ".nfo", ".jpg", ".srt"] }
]
}
}
}Filesystem plugin
{
"id": "aviato-fs-local",
"name": "Local Filesystem",
"version": "1.0.0",
"description": "Add local files to your libraries",
"author": "Aviato",
"license": "MIT",
"engine": "bun",
"entry": "src/index.ts",
"aviato": { "minVersion": "0.1.0" },
"capabilities": ["filesystem"],
"mediaTypes": ["movies", "tv", "music", "photos", "books"],
"configuration": [
{ "key": "excludePatterns", "label": "Exclude patterns", "description": "Glob patterns to exclude from scanning", "input": "string-list" },
{ "key": "watchForChanges", "label": "Watch for changes", "input": "toggle", "default": true }
],
"capabilityConfig": {
"filesystem": {
"supportsWatch": true,
"supportsLocalFileAccess": true,
"supportsWrite": true
}
}
}Pure subscriber (hook plugin)
{
"id": "aviato-thumbnails",
"name": "Thumbnail Generator",
"version": "0.1.0",
"description": "Generates thumbnails from media files using FFmpeg when no artwork exists",
"author": "Aviato",
"license": "MIT",
"engine": "bun",
"entry": "src/index.ts",
"aviato": { "minVersion": "0.1.0" },
"capabilities": [],
"subscriptions": {
"hooks": [
{ "name": "pipeline.probe.afterProcess", "order": 90, "requiresLocalFile": true }
]
}
}requiresLocalFile: true here declares that this hook shells out to
FFmpeg against a real on-disk path, so the file must be materialized
locally (via the source filesystem plugin's getLocalPath, or by
fetching to a scratch directory) before the hook fires. The
orchestrator wiring that consumes this flag is planned but not yet
implemented; declaring it now is forward-compatible.
Validation rules
The schema is strict: unknown top-level fields cause validation to fail. The full enforced rules:
idis lowercase kebab-case.versionmatches<n>.<n>.<n>(extra suffix allowed).capabilitiesis an array of'filesystem' | 'indexer' | 'library'. Empty is valid (pure subscriber).enginemust be one of the four runtimes.- Subscription names match
^[a-z0-9*]+(\.[a-z0-9*]+)*$. - Hook
orderis an integer in[0, 100]. - Hook
requiresLocalFileis a boolean (defaultfalse). rateLimit.requests[].windowmatches\d+[smh].
A failed validation surfaces in the admin UI as a Zod error path
(for example capabilities.0: ..., aviato.minVersion: ...) so
the cause is obvious.