AviatoAviato
DeveloperPlugins

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-commas

2. 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 install

4. 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-commas

6. 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 test

7. Where to go next

You've got the smallest possible plugin running. Pick the doc that matches your goal:

GoalDoc
Add a new library type (movies, audiobooks, ...)library.mdx
Identify files against a remote APIindexer.mdx
Discover files from a new source (S3, Dropbox, network share)filesystem.mdx
React to more pipeline events or extend the UIhooks.mdx
Surface a settings form, action button, or metadata panelui-schemas.mdx
Add user-editable settings to your pluginconfiguration.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/plugins if its manifest is invalid.
  • capabilities: [] plus no subscriptions. The plugin loads but does nothing. Aviato won't ever call you.
  • Top-level await in the entry file. Don't. The plugin must signal ready via createPlugin()'s implicit signalReady() 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

On this page