Getting Started
Build, install, and iterate on your first Aviato plugin from an empty directory to a running process.
We'll build a tiny hook plugin called aviato-tres-commas. It watches
every ingestion and tags media files as belonging to the Three Comma
Club. It's the smallest meaningful plugin shape: zero capabilities, one
hook subscription, ten lines of logic. Once it works, you'll know enough
to extend the same scaffold into a library or indexer plugin.
Prerequisites
- A running Aviato server you can restart and view logs for.
- Bun installed locally (or Node, if you set
engine: "node"in the manifest below). - Ten minutes.
1. Pick where the plugin lives
Aviato discovers plugins from <data-dir>/plugins/ at server startup.
Each subdirectory with a valid plugin.json becomes a plugin.
Make a directory for your plugin inside that location. For local development you can symlink it from anywhere convenient.
mkdir -p <data-dir>/plugins/tres-commas/src
cd <data-dir>/plugins/tres-commas2. Write the manifest
plugin.json is the only file the server reads to decide whether to
load the plugin. Pure-subscriber plugins declare capabilities: [] and
one or more subscriptions:
{
"id": "aviato-tres-commas",
"name": "Three Commas",
"version": "0.1.0",
"description": "Tags every ingested item with three-comma vibes",
"author": "Russ Hanneman",
"license": "MIT",
"engine": "bun",
"entry": "src/index.ts",
"aviato": { "minVersion": "0.1.0" },
"capabilities": [],
"subscriptions": {
"hooks": [
{ "name": "pipeline.probe.afterProcess", "order": 95 }
]
}
}order: 95 puts us late in the chain, after every other hook plugin
has contributed, so we can read everything they produced.
See manifest.mdx for the full field reference.
3. Add a package
// package.json
{
"name": "tres-commas",
"version": "0.1.0",
"type": "module",
"scripts": {
"test": "bun test"
},
"dependencies": {
"@aviato/plugin-sdk": "^1.0.0"
},
"devDependencies": {
"bun-types": "latest"
}
}// tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"outDir": "./dist",
"rootDir": "./src",
"types": ["bun-types"]
},
"include": ["src"]
}Install dependencies:
bun install4. Implement the entry point
// src/index.ts
import { createPlugin } from '@aviato/plugin-sdk'
const plugin = createPlugin({})
plugin.hooks.on('pipeline.probe.afterProcess', async ({ bundle }) => {
const primary = (bundle as { files?: { media: { type: string }[] } })
.files?.media.find(f => f.type === 'primary')
if (!primary) return null
return {
tags: {
'tres-commas': 'true',
},
}
})That's the entire plugin. createPlugin returns the subscription
emitters, you wire one handler, the SDK auto-registers the
hook.dispatch RPC method on first .on(), and the plugin signals
ready. Aviato invokes the hook, gets the BundleDelta, and merges
tags['tres-commas'] = 'true' into the bundle before persisting.
See bundle.mdx for the full delta merge semantics.
5. Restart the server
The first time you add a new plugin directory, restart the Aviato
server so it picks the plugin up. After that, you only need to restart
the plugin itself when its code changes. Use the restart button on the
plugin's admin page (/admin/plugins/aviato-tres-commas) or disable
and re-enable it.
You should see in the server logs:
Plugin discovered pluginId=aviato-tres-commas
Plugin started pluginId=aviato-tres-commas6. Verify it ran
Trigger a scan of any library, then open
/admin/plugins/aviato-tres-commas/logs. Every item that passed
through the probe phase should produce a log line with the bundle
delta you returned. (Hook result logging is gated on the server's
debug flag; enable it from settings if you don't see entries.)
For a faster feedback loop, write a unit test that exercises your handler directly:
// src/__tests__/index.test.ts
import { test, expect } from 'bun:test'
test('tags primary files', async () => {
const fakeBundle = { files: { media: [{ type: 'primary', uri: 'file://a.mkv' }], auxiliary: [] } }
// import and call your handler directly, asserting on the returned delta
})bun test7. Where to go next
You've got the smallest possible plugin running. Pick the doc that matches your goal:
| Goal | Doc |
|---|---|
| Add a new library type (movies, audiobooks, ...) | library.mdx |
| Identify files against a remote API | indexer.mdx |
| Discover files from a new source (S3, Dropbox, network share) | filesystem.mdx |
| React to more pipeline events or extend the UI | hooks.mdx |
| Surface a settings form, action button, or metadata panel | ui-schemas.mdx |
| Add user-editable settings to your plugin | configuration.mdx |
Common pitfalls
- Forgot to restart the server after adding the directory. Aviato scans the plugin directory once at boot.
- Manifest validation failed silently. Check the server log for
the Zod error path. The plugin won't appear in
/admin/pluginsif its manifest is invalid. capabilities: []plus nosubscriptions. The plugin loads but does nothing. Aviato won't ever call you.- Top-level
awaitin the entry file. Don't. The plugin must signal ready viacreatePlugin()'s implicitsignalReady()before the server considers it healthy, and top-level await delays that. - Mutating the bundle in-place. Hooks return a
BundleDelta; they don't mutate the input. Mutation works today by accident but is not part of the contract.
See also
- Plugin system overview
- Plugin manifest
aviato-embedded-metadata: the simplest production hook plugin shipped with Aviato. Read it before writing anything ambitious.