JavaScript SDK
The JavaScript SDK, @owncast/plugin-sdk, is the most common way to write an Owncast plugin. You write JavaScript or TypeScript, and the CLI bundles it into a single installable plugin that runs sandboxed inside the Owncast server. If you're choosing an authoring path, see the plugins overview.
The plugin SDKs are brand-new in Owncast 0.3.0 and the API is still evolving. If you hit a bug or have a suggestion, please open an issue or chat live with the community.
This page is the JavaScript-specific layer: scaffolding, definePlugin, the CLI, and TypeScript. Handlers, APIs, permissions, and the manifest work the same in both SDKs and have their own reference pages.
How it maps to the reference docs
The shared reference names APIs in their canonical form, which is the JavaScript form: so you can read it as-is. Quick orientation:
| In the reference | In JavaScript |
|---|---|
| Define a handler | a method on definePlugin({ ... }) |
Handler for an event (e.g. chat.message.received) | onChatMessage(msg): camelCase, on + the event |
Call a host API (e.g. owncast.chat.sendAction) | identical: owncast.chat.sendAction(text) |
| Payload fields | camelCase: msg.user.displayName, msg.clientId |
| Filter result | filter.pass() / filter.modify(payload) / filter.drop(reason) |
| Subscribe to a custom event | on: { "my.event"(payload) { … } } |
| Build / test your plugin | npm run package / npm test |
Prerequisites
- An Owncast server you can administer, version 0.3.0 or newer.
- Node.js 18 or newer (
node --versionto check).
Scaffold a new plugin
You don't install the SDK by hand. Scaffold a project with create-owncast-plugin and the generated package.json already lists @owncast/plugin-sdk as a dependency:
npx create-owncast-plugin@latest my-plugin
cd my-plugin
npm install # fetches the test and serve helpers
Pass the slug you want as the argument. The scaffold uses it for the directory name, the output filename, and the URL prefix. Slugs are lowercase letters, digits, and hyphens, starting with a letter.
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).
npm install runs a postinstall step that fetches the prebuilt test and serve host binaries (the scenario runner and the dev server). Building and packaging a plugin need no download. This postinstall is the only network step, and everything after is local.
Write a plugin
A plugin is the object you pass to definePlugin. Define a method for each event you want to react to: the SDK derives the manifest's subscription list from which methods are present, so there's no separate list to keep in sync.
const { definePlugin, owncast, filter } = require('@owncast/plugin-sdk');
module.exports = definePlugin({
onChatMessage(msg) {
owncast.chat.send(`echo: ${msg.body}`);
},
filterChatMessage(msg) {
return msg.body.includes('spam') ? filter.drop('spam') : filter.pass();
},
});
The package exports four things you'll use:
definePlugin(handlers): registers your handlers and returns the plugin object to export.owncast: the host API namespace (owncast.chat.send(...),owncast.kv.get(...), and the rest). Method names are camelCase. Each call is gated by the matching permission you declare in your manifest. See the APIs reference.filter: the constructor for filter results:filter.pass(),filter.modify(payload),filter.drop(reason). Used only fromfilterChatMessage.authCheck: verdict helpers for theonAuthCheckhandler of anauth.gateplugin:authCheck.ok(),authCheck.refresh({ ttl? }),authCheck.deny(reason?).
Handler names are camelCase and map to the runtime events listed in the handlers reference: onChatMessage, filterChatMessage, onChatUserJoined, onStreamStarted, onTick, onFediverseFollow, onHttpRequest, and so on. Payload fields are camelCase too (msg.user.displayName, msg.clientId).
Beyond top-level methods, custom-event handlers are passed as a nested object keyed by event type: on: { "my.event"(payload) {} }. Dynamic viewer pages use plain functions. onTabContent(ctx) receives the requested manifest.tabs object key as ctx.slug. onPageContent(ctx) receives manifest.extraPageContent.slug. Two more take no key: onPageStyles() and onPageScripts() return CSS and JavaScript injected into the viewer page at request time, gated on ui.modify. Rather than hand-rolling prefix parsing in onChatMessage, you can declare a commands table that the host's built-in !help picks up automatically. Both are shown for JavaScript on the subject pages: Handlers, Commands, and UI.
TypeScript
The package ships index.d.ts, so you get autocomplete and type-checking on every event payload and host API with no extra setup. Name your entry src/plugin.ts and the CLI compiles it the same way:
import { definePlugin, owncast, filter, ChatMessage } from '@owncast/plugin-sdk';
export default definePlugin({
onChatMessage(msg: ChatMessage) {
owncast.chat.send(`echo: ${msg.body}`);
},
});
The build detects src/plugin.ts, src/plugin.js, plugin.ts, or plugin.js in that order. Types are declarations only: there's no separate compile step or tsconfig required.
The CLI
The SDK installs an owncast-plugin CLI, exposed through the package.json scripts the scaffold writes:
| Command | Script | What it does |
|---|---|---|
owncast-plugin build | npm run build | Bundles src/plugin.{js,ts} into an intermediate build artifact |
owncast-plugin test | npm test | Builds, then runs the __tests__/ scenarios through the real runtime |
owncast-plugin serve | npm run serve | Local dev server at http://localhost:8080/plugins/<slug>/ |
owncast-plugin package | npm run package | Builds and bundles everything into <slug>.ocpkg: the file you ship |
npm run package # produces my-plugin.ocpkg
npm test # runs your scenarios
npm run serve # iterate against a local dev server
npm run package only rebuilds when the bundle is missing. After changing source, run npm run build first so the package doesn't ship stale code.
The .ocpkg is the single distribution artifact: it contains your manifest, the bundled code, your public/ and assets/ directories, and an optional icon.png and INSTRUCTIONS.md. See Packaging & distribution for what goes inside and how to install it.
In JavaScript, npm test runs __tests__/*.test.js files calling runScenarios (build the array with loops, helpers, and fixtures), or static __tests__/*.test.json files. The full scenario data model and the local dev server (npm run serve) are on the Testing page.
Constraints to know
The CLI bundles your code into a single file that runs inside the server's sandbox, not in Node. That sandbox shapes how you write a plugin:
- Use
owncast.http.fetchfor outbound HTTP, not the globalfetch,axios, or a package that wraps Node'shttp. Network access goes through the host API and is gated on thenetwork.fetchpermission. See the APIs reference. - Not every npm package works. Pure-JavaScript packages bundle in fine. Anything that needs the Node.js runtime does not. See Third-party libraries.
Third-party libraries
npm packages work only if they're pure JavaScript. A plugin runs in a sandbox, not Node, so a package that touches fs, net, http/https, path, crypto, process, or child_process bundles cleanly and then throws when that code runs.
A package can also hit a Node built-in on a path you never exercise, so test the parts you use. For outbound HTTP, use owncast.http.fetch, not an HTTP-client package.
The page-content-demo example uses the mustache package this way.
What's in the package
index.js: the runtime withdefinePlugin, command handlers, theowncast.*host wrappers, and filter helpers.index.d.ts: TypeScript declarations for every event payload and host API.testing.js: therunScenarios/runScenarioFilestest API.bin/owncast-plugin: the CLI (build,test,serve,package).scripts/postinstall.js: fetches the prebuilt test and serve host binaries on install, used bynpm testandnpm run serve.
Where to go next
- Handlers reference: every event you can subscribe to and its payload shape.
- APIs reference: every
owncast.*method and the permission it needs. - Testing: the full scenario data model.
- Packaging & distribution: building the
.ocpkgand installing it. - Example plugins: one per feature, each a complete starting point you can copy.
- SDK source: the
@owncast/plugin-sdkpackage and toolchain.
Improve this page
See something missing or incorrect? Edit this page and improve the documentation for everyone.
Related Documents
- Python SDKAuthor Owncast plugins in Python with owncast-plugin-py: install, the @plugin decorators, the owncast-plugin-py CLI, and testing.
- Extend Owncast with pluginsWrite plugins that run sandboxed inside Owncast to react to chat, post to the fediverse, serve HTTP endpoints, and add UI.
- Add custom JavaScriptRun custom JavaScript on your Owncast web page.
- Plugin quickstartScaffold a new Owncast plugin in JavaScript or Python, build it, package it, and install it on your server.
- Configuration via PluginsLet admins configure your plugin with typed settings. Owncast renders the form, you read the values at runtime with owncast.config.get.
- Testing pluginsDrive your built plugin through the real Owncast runtime with mocked side effects. Scenario tests, assertions, fixtures, HTTP auth.
