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:
- Chat event handlers so your plugin can react when people talk, join, leave, or rename themselves.
- Chat and user APIs so your plugin can post messages, inspect chat state, and moderate users.
- 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.timeror 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:
- JavaScript
- Python
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}`);
},
});
from owncast_plugin import plugin, owncast
@plugin.on_chat_message
def echo(msg):
name = msg.user.display_name if msg.user else "someone"
owncast.chat.send(f"{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:
- JavaScript
- Python
module.exports = definePlugin({
onChatMessage(msg) {
owncast.chat.send(`echo: ${msg.body}`);
},
});
@plugin.on_chat_message
def echo(msg):
owncast.chat.send(f"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.
- JavaScript
- Python
owncast.chat.send("hello chat");
owncast.chat.sendAction("waves"); // /me-style action message
owncast.chat.system("Stream starting in 5 minutes");
owncast.chat.send("hello chat")
owncast.chat.send_action("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.
- JavaScript
- Python
module.exports = definePlugin({
onChatMessage(msg) {
if (!owncast.chat.replyTo(msg, "psst: got your message")) {
owncast.chat.send("got your message"); // sender already disconnected
}
},
});
@plugin.on_chat_message
def whisper(msg):
if not owncast.chat.reply_to(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.
- JavaScript
- Python
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();
},
});
from owncast_plugin import plugin, filter
@plugin.filter_chat_message
def clean(msg):
if "spam" in msg.body:
return filter.drop("spam keyword")
if "damn" in msg.body:
return filter.modify({**msg.raw, "body": msg.body.replace("damn", "****")})
return filter.pass_() # trailing underscore: pass is a keyword
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 usingmsg.timestampfor 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.
Related Documents
- Chat plugin commandsDeclare chat commands with aliases, cooldowns, moderator gating, and automatic !help listings.
- Chat moderationAdd moderators, remove messages and users from your chat.
- Extend Owncast with pluginsWrite plugins that run sandboxed inside Owncast to react to chat, post to the fediverse, serve HTTP endpoints, and add UI.
- ChatHow to use the Owncast chat features.
- PluginsAn overview of Owncast plugins, what they can do for your stream, and how to install one from the admin.
- Owncast Plugin APIsEvery owncast.* method your plugin can call from inside a handler, what it returns, and what permission it needs.
