Skip to main content

Chat plugins

If you want to build a plugin that talks in chat, reacts to viewers, or moderates messages, this is the page to start with. Code samples are shown in both supported languages. Set up your toolchain on the JavaScript or Python SDK page first.

Owncast exposes chat functionality in three layers:

  1. Chat event handlers so your plugin can react when people talk, join, leave, or rename themselves.
  2. Chat and user APIs so your plugin can post messages, inspect chat state, and moderate users.
  3. Chat filters so your plugin can rewrite or drop messages before viewers see them.

What you can build

  • Chat bots that reply to commands or keywords.
  • Welcome bots that greet people when they join.
  • Reminder bots that post messages when the stream starts.
  • Countdown and timer bots powered by owncast.timer or the tick handler.
  • Moderation helpers that hide messages, disconnect clients, or disable abusive users.
  • Filters that rewrite, translate, or drop messages before they are broadcast.

A reply bot is just one handler:

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

module.exports = definePlugin({
onChatMessage(msg) {
const name = msg.user?.displayName ?? "someone";
owncast.chat.send(`${name} said: ${msg.body}`);
},
});

Reacting to chat

Define onChatMessage (@plugin.on_chat_message in Python) to see each message after filters run, just before it broadcasts to viewers:

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

The fields you reach for most are msg.body (the raw text), msg.user (the sender identity, with user.id for per-user state and user.scopes for moderator checks), and msg.timestamp (deterministic, so prefer it over the clock when comparing elapsed time or asserting in tests). Don't key state or permissions off display names.

For the full message payload and every other event a chat plugin can subscribe to (user join and part, rename, moderation, and more), see the Events reference.

Sending chat messages

owncast.chat.send

Post a chat message. Sent as your plugin's bot identity. Takes plain text, not markup: the chat UI HTML-escapes it on display, so characters like <, &, and " render as text rather than HTML.

owncast.chat.send("hello chat");
owncast.chat.sendAction("waves"); // /me-style action message
owncast.chat.system("Stream starting in 5 minutes");

Requires chat.send.

owncast.chat.sendAction

Post an action-style (/me) message: sendAction in JavaScript, send_action in Python. Like send, takes plain text and is HTML-escaped by the chat UI on display.

Requires chat.send.

owncast.chat.system

Post a server-announcement message. No bot identity is attached. The body renders inline as HTML. Use this for short, server-attributed notices like "Stream starting in 5 minutes". Treat the body as untrusted HTML output: don't interpolate viewer-controlled input without escaping it.

Requires chat.send.

Chat identity

Every plugin has exactly one chat identity: the bot Owncast provisions when your plugin is installed. Its display name is your manifest's bot.displayName if set, otherwise name.

Both send and sendAction post as this identity through Owncast's normal chat pipeline, including filters, rate limits, and moderation. Plugins cannot post under arbitrary names or impersonate real users.

The bot user is keyed on the plugin's slug, so the identity survives manifest edits to name or bot.displayName. If you need multiple chat personas, ship multiple plugins.

Reading chat state

owncast.chat.history

Return the most recent chat messages (an optional limit defaults to 50). Each entry has the shape { id, user?, clientId?, body, timestamp }.

Requires chat.history.

owncast.chat.clients

Return the list of currently connected chat clients: { id, userId?, displayName?, connectedAt?, userAgent?, ipAddress?, messageCount? }. The id is the per-connection client ID used by owncast.chat.kick.

Requires chat.history.

owncast.server.emotes

Read the server's custom chat emotes ({ name, url }) when your bot wants to reference or mirror the emote catalog.

Requires server.read.

owncast.users.list and owncast.users.get

Read the chat user list or a single user record by id.

Requires users.read.

Moderation APIs

These are deleteMessage / kick / sendTo / replyTo in JavaScript and delete_message / kick / send_to / reply_to in Python.

owncast.chat.deleteMessage

Hide a chat message from viewers, by message id.

Requires chat.moderate.

owncast.chat.kick

Disconnect a chat client, by client id.

Requires chat.moderate.

owncast.chat.sendTo

Send a private message to a single connected client, by client id.

Requires chat.send.

owncast.chat.replyTo

Whisper a reply back to whoever sent a chat message. You can pass either the full message object from the chat-message / filter handler, or a bare client id if that's all you have. It returns a falsy value when the sender connection is no longer known, which gives you a clean fallback to a public message.

module.exports = definePlugin({
onChatMessage(msg) {
if (!owncast.chat.replyTo(msg, "psst: got your message")) {
owncast.chat.send("got your message"); // sender already disconnected
}
},
});

Requires chat.send.

Commands

For chat commands, declare a command table with aliases, cooldowns, moderator gating, and automatic !help listings. See Chat commands.

Moderating users

owncast.users.setEnabled

Enable or disable a chat user, by id, with an optional reason: setEnabled in JavaScript, set_enabled in Python.

Requires users.moderate.

owncast.users.banIP

Ban an IP from joining chat: banIP in JavaScript, ban_ip in Python.

Requires users.moderate.

Chat filters

Filters see chat messages before they're broadcast, with the ability to rewrite or drop them. Filters run lowest-priority first. A drop ends the chain and the message never reaches later filters or notifications. A modify passes the new payload to the next filter.

filterChatMessage

Receives the same ChatMessage shape as the chat-message handler and returns one of three results, built with the filter helper:

  • pass: let the message through unchanged.
  • modify: replace it with a new payload.
  • drop: drop it (with a reason). The chain stops here.
const { definePlugin, filter } = require("@owncast/plugin-sdk");

module.exports = definePlugin({
filterChatMessage(msg) {
if (msg.body.includes("spam")) return filter.drop("spam keyword");
if (msg.body.includes("damn")) {
return filter.modify({ ...msg, body: msg.body.replace("damn", "****") });
}
return filter.pass();
},
});

Requires the chat.filter permission. The host rejects the load if a plugin defines the filter handler without declaring that permission.

Filter priority (optional)

Lower numbers run earlier. Default 100. Set it with filterPriority (JavaScript) on the plugin definition, or by calling plugin.set_filter_priority(priority) (Python).

Use this when your plugin's behavior depends on whether other filters have already run. For example, a profanity filter should usually run before a translator.

Filter safety

  • Errors are treated as a pass. A throwing filter never blocks chat.
  • Filters are time-capped at 50 ms. A slow filter is cancelled and treated as pass.
  • After 5 consecutive failures (errors or timeouts) the plugin is auto-disabled for the rest of the session. A successful filter call resets the counter.

Host-enforced limits that matter for chat plugins

A few host limits are worth designing around:

  • filter runtime: 50 ms per message
  • event-handler runtime (chat-message, user-joined, etc.): 500 ms per call
  • hard per-call ceiling: 10 s
  • filter output size: 1 MiB
  • pending timers: 64 at once
  • timer delay range: 100 ms to 24 h

That means chat bots and filters should stay lightweight, avoid slow network round-trips in the hot path, and keep rewritten payloads small.

Permissions you'll commonly need

  • chat.send: post chat messages and private replies.
  • chat.history: read recent chat messages and connected clients.
  • chat.moderate: hide messages and disconnect clients.
  • chat.filter: rewrite or drop messages before broadcast.
  • users.read: inspect user records.
  • users.moderate: disable chat users or ban IPs.

See Permissions for the complete security model.

Example chat plugins

The plugin SDK ships small chat-focused examples that map closely to the patterns on this page (each has both a JavaScript and a Python version):

  • echo-bot: the smallest possible reply bot using the chat-message handler + owncast.chat.send.
  • chat-logger: logs every chat message without replying.
  • stream-tracker: combines chat commands, chat-user lifecycle handlers, and action announcements.
  • profanity-filter: rewrites messages without dropping them.
  • slow-mode: drops messages using msg.timestamp for rate limiting.
  • engagement-bot: moderates by deleting a message.
  • timer-bot: reminder/countdown bots driven from chat, using timers and the tick handler.

Browse them at examples/js · examples/python.

Where this fits with the other plugin docs

  • Choosing an SDK and the JavaScript / Python pages cover the language-specific setup, CLI, and syntax.
  • Chat commands covers command tables, the automatic !help, and mixing commands with your own chat handlers.
  • Event handlers is the full handler reference for all plugin events.
  • Owncast APIs is the full API reference for all owncast.* methods.
  • Manifest reference covers permissions, bot identity fields, and every manifest property.
  • Contributing UI covers viewer-side UI, overlays, buttons, scripts, and styles if your chat plugin also ships frontend pieces.

If you're starting from scratch, read Quickstart first and then come back here.


Improve this page

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

Contributors to this documentation

Related Documents