Skip to main content

Plugin quickstart

The quickest way to build a plugin is with the JavaScript or Python SDK. Pick a tab below and follow it through installation. To use Rust, TinyGo, AssemblyScript, Zig, or another compiled language instead, see Native WebAssembly.

Prerequisites

  • An Owncast server you can administer, version 0.3.0 or newer.
  • Node.js 18 or newer (node --version to check) for the @owncast/plugin-sdk toolchain.

1. Create a new plugin

A plugin's identifier is its slug: lowercase letters, digits, and hyphens, starting with a letter. It's used as the directory name, the output filename, and the URL prefix.

Scaffold a project with create-owncast-plugin, passing the slug:

npx create-owncast-plugin@latest my-plugin
cd my-plugin
npm install

You now have:

my-plugin/
├── package.json
├── plugin.manifest.json display name, slug, version, permissions
├── README.md how to build, test, package, and install it
├── INSTRUCTIONS.md optional, rendered as a tab in the admin
├── AGENTS.md notes for AI coding agents
├── .agents/ a bundled skill for AI coding agents
├── src/
│ └── plugin.js your code, with a sample handler
└── __tests__/
└── plugin.test.js a sample scenario test

npm install also creates node_modules/. None of these are created for you, but you can add an icon.png (shown in the admin plugin list), a public/ directory (static files served at /plugins/my-plugin/), and an assets/ directory (files the host inlines for manifest fields).

The manifest has both a human-readable display name ("name": "My Plugin") and a slug ("slug": "my-plugin"). The display name is what admins see in lists. The slug is the canonical identifier. See the manifest reference for the rules.

2. Write some code

A handler reacts to an event. The SDK derives the manifest's subscription list from which handlers you define, so there's nothing else to keep in sync. Here's an echo bot:

Open src/plugin.js:

const { definePlugin, owncast } = require('@owncast/plugin-sdk');

module.exports = definePlugin({
onChatMessage(msg) {
owncast.chat.send(`echo: ${msg.body}`);
},
});

See the handlers reference for everything you can hook into, and the APIs reference for every owncast.* method.

3. Build the plugin

This produces my-plugin.ocpkg in your project root: a single file containing your manifest, the compiled plugin, and the contents of public/ and assets/. The .ocpkg is the distribution format: that one file is everything an admin needs.

npm run package

4. Run the tests

Each scenario fires events through the real plugin runtime with mocked side effects, so a passing test means the same behavior in production. See the testing guide for the full data model.

npm test

5. (Optional) iterate against a local dev server

Serves the plugin at http://localhost:8080/plugins/my-plugin/ for curling endpoints, opening static pages in a browser, or triggering event handlers through the /_dev/ helper endpoints (for example POST /_dev/chat). Restart the dev server when you change your code.

npm run serve

6. Install on your server

In the Owncast admin, open Plugins in the sidebar and click Upload plugin. Pick the my-plugin.ocpkg file your build produced. The plugin appears in the list immediately. Toggle Enabled to load it.

The Plugins page in the admin, listing installed plugins with their requested permissions, status, an enable toggle, and Upload plugin and Configure buttons

Alternatively, copy my-plugin.ocpkg to your server's data/plugins/ directory and the next scan tick will pick it up:

scp my-plugin.ocpkg user@your-owncast-server:/path/to/owncast/data/plugins/

If the plugin declares permissions, the admin reviews them in the Permissions tab on the plugin's detail page before enabling. The first enable captures the approved permission set. If you later ship an update that asks for more access, the already-approved version keeps running with its existing permissions while the update waits as pending until the admin re-approves.

The Permissions tab on a plugin's detail page, listing each requested permission with a plain-language description

When things go wrong

  • The plugin doesn't appear in the admin list. Make sure the .ocpkg is in data/plugins/, not just plugins/, and the filename ends in .ocpkg. The admin Plugins page has a Refresh button if you don't want to wait for the next scan tick.
  • The plugin appears but won't enable. Check the admin's plugin detail view. The Status column shows error if the manifest is invalid or the plugin failed to instantiate. Hover for the message, or run your tests locally to catch the same problem before shipping.
  • The plugin enables but does nothing. Make sure you're using the right handler name (onChatMessage / on_chat_message, not onMessage) and that the matching permission is in your manifest. A call without its permission never reaches Owncast: the denial is logged on the server and the call returns an empty or zero value, so watch the Owncast logs.
  • The plugin is auto-disabled. A filter that throws or hangs five times in a row gets disabled for the rest of the session. Fix the bug, rebuild, redeploy, and re-enable.

Improve this page

See something missing or incorrect? Edit this page and improve the documentation for everyone.

Contributors to this documentation

Related Documents