Plugin Manifest reference
Cada complemento tiene un archivo plugin.manifest.json en su raíz. Esta es la fuente de verdad para la identidad del complemento, los permisos que necesita, los destinos de red a los que se le permite llamar, las páginas de administración que contribuye, y los botones de acción que agrega a la interfaz del visualizador.
Plugins require Owncast 0.3.0 or later.
El manifiesto es lo que un administrador revisa antes de instalar el complemento. El host lo analiza en el tiempo de carga y hace cumplir cada declaración. Nada en el complemento compilado puede otorgar una capacidad que el manifiesto no pidió.
El manifiesto es un JSON sencillo que describe el complemento al host, independiente del lenguaje en que escribiste el código. Para detalles específicos del idioma, consulta la referencia de SDK de JavaScript o Python.
Manifiesto mínimo
{
"api": "1",
"name": "My Plugin",
"version": "0.1.0",
"description": "Short description for admins",
"permissions": []
}
api, name, y version son obligatorios. Todo lo demás es opcional y solo necesario cuando usas la función correspondiente.
Campos de nivel superior
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
api | cadena | sí | Versión del esquema del manifiesto. Actualmente "1". |
name | cadena | sí | Nombre que se muestra en listas de administradores y tarjetas de registro. Ejemplo: "Bot Asombroso de Eco". |
slug | cadena | no | Identificador canónico (prefijo de URL, espacio de nombres de configuración, nombre de archivo). Autogenerado de name si se omite. Ver abajo. |
version | cadena | sí | La versión de tu complemento. SemVer recommended. Informational metadata for admins and the registry: the host doesn't gate loading on it. |
description | cadena | no | Resumen de una sola oración que el administrador ve en la lista de complementos y durante la instalación. |
category | cadena | no | Registry browse category. See category. |
permissions | cadena[] | no | Lista de capacidades que tu complemento necesita. Ver Permisos. |
config | objeto | no | Configuraciones configurables por el administrador que tu complemento lee en tiempo de ejecución. Ver Configuración. |
bot | objeto | no | Configuración del chat-bot. Ver bot. |
network | objeto | no | Lista blanca de HTTP salientes, requerida cuando se concede network.fetch. Ver abajo. |
actions | objeto[] | no | Botones de acción que se agregan a la interfaz del visualizador. Ver UI: Botones de acción. |
admin | objeto | no | Páginas de administración que se añaden a la interfaz de administración de Owncast. Ver UI: Páginas de administración. |
styles | cadena[] | no | Archivos CSS incrustados en la página del visualizador. Ver styles. |
scripts | cadena[] | no | Archivos JavaScript incrustados en la página del visualizador. Ver scripts. |
extraPageContent | objeto | no | Un objeto que declara un slug y un archivo HTML opcional que se prefiere al bloque de contenido extra del visualizador. Ver extraPageContent. |
tabs | object | no | Viewer-page tabs keyed by stable slug. Ver tabs. |
name y slug
name es el nombre de visualización legible por humanos. Puede contener cualquier carácter, incluidos espacios y puntuaciones, y es lo que los administradores ven en la lista de complementos, lo que aparece en las tarjetas de exploración de registros y la identidad predeterminada del chat-bot.
slug es el identificador canónico. Controla:
- El prefijo de URL del complemento:
/plugins/<slug>/... - El espacio de nombres de configuración (almacenamiento clave-valor)
- El nombre del archivo del artefacto construido (
<slug>.ocpkg) - La clave principal en el registro de complementos
Los slugs son letras minúsculas, dígitos y guiones, comenzando con una letra, hasta 64 caracteres. El SDK deriva uno de name automáticamente cuando se omite slug: los espacios y puntuaciones colapsan en guiones simples, letras en minúsculas. "Bot Asombroso de Eco" se convierte en bot-asombroso-de-eco. Fija slug explícitamente cuando la auto-derivación no es lo que deseas, o cuando tu nombre de visualización utiliza caracteres fuera de ASCII ("Ayudante de Café" de otro modo daría lugar a ayudante-de-cafe).
Evita cambiar el slug después del lanzamiento: el cambio de nombre parecerá un complemento diferente para los administradores, con un nuevo almacén de configuración. Cambiar name (solo visualización) es seguro. No cambia la identidad.
category: registry browse category
An optional label that places your plugin in a browse category on the registry and in the admin UI. The canonical values are chat-bots, chat-filters, moderation, authentication, themes, overlays, notifications, integrations, video, analytics, games, admin-utilities, examples, and other.
The SDK's packaging CLI warns when category isn't one of these, but nothing rejects it: the host and registry tolerate unknown categories, they just won't match any browse filter.
bot: identidad del chat-bot
Los complementos que publican en el chat (usando owncast.chat.send) aparecen bajo un usuario del chat-bot. Por defecto, el bot aparece bajo el name de visualización del complemento. Sustituye eso con bot.displayName:
{
"name": "Stream Sidekick",
"bot": {
"displayName": "Sidekick"
}
}
En el chat, el bot publica como "Compañero" en lugar de "Compañero de Transmisión". La primera vez que se carga el complemento, Owncast provisiona un usuario de chat persistente claveado en el slug del complemento (por lo que la identidad del bot sobrevive reinstalaciones y cambios de nombre de visualización).
bot.displayName solo es relevante para los complementos que tienen el permiso chat.send. Se ignora de otra manera.
config: configuraciones configurables por el administrador
Declara configuraciones tipadas aquí y Owncast renderiza un formulario editable para ellas en el administrador, que tu complemento lee en tiempo de ejecución con owncast.config.get. Cada entrada tiene un type (string, number, o boolean), un default, y una description:
{
"config": {
"greeting": { "type": "string", "default": "welcome!", "description": "First-join message" },
"cooldownMs": { "type": "number", "default": 2000, "description": "Per-user command cooldown" },
"modOnly": { "type": "boolean", "default": false, "description": "Restrict to moderators" }
}
}
Config keys starting with __ are reserved: the host uses that prefix to inject per-instance state into the plugin runtime, and a manifest declaring one is rejected at load.
Cobertura completa, incluyendo cómo se renderiza el formulario, enmascaramiento de credenciales, validación y dónde se almacenan los sobrescrituras, en Configuración.
permissions
Cada entrada desbloquea un fragmento de APIs del host. El host rechaza llamadas a un método cuyo permiso no declaraste.
{
"permissions": ["chat.send", "storage.kv", "network.fetch"]
}
Consulta la referencia de permisos para la lista completa de identificadores y lo que cada uno otorga.
network: lista blanca de HTTP salientes
network.fetch está restringido por una lista de permitidos de nombres de host explícitos. Si declaras network.fetch en permissions, también necesitas un campo network.allowedHosts que enumere los hosts que llamarás:
{
"permissions": ["network.fetch"],
"network": {
"allowedHosts": ["api.discord.com", "*.weather.com"]
}
}
Las entradas son comodines de nombres de host. Nombres simples como api.discord.com coinciden exactamente. * es un segmento comodín, por lo que *.weather.com coincide con api.weather.com y data.weather.com pero no con weather.com por sí mismo o evil.com.
El comodín "*" coincide con cualquier host, pero debes escribirlo explícitamente:
{
"network": { "allowedHosts": ["*"] }
}
Esto es intencional. Los administradores que revisan el manifiesto ven el alcance que están otorgando. La mayoría de los complementos deberían listar los hosts específicos que llaman en su lugar.
El host rechaza la carga si se concede network.fetch sin una entrada allowedHosts.
actions: botones de acción
Los botones de acción son entradas clicables que Owncast muestra debajo de la transmisión. Mientras tu complemento esté habilitado, el host fusiona sus entradas en la lista que Owncast ya muestra.
{
"permissions": ["ui.modify", "http.serve"],
"actions": [
{
"title": "Chat Overlay",
"description": "Open the live chat overlay",
"url": "/",
"icon": "/star.png",
"color": "#3b82f6"
},
{
"title": "Issue tracker",
"url": "https://github.com/example/my-plugin/issues",
"openExternally": true
},
{
"title": "About this stream",
"html": "<p>Live every weekday at 8pm UTC.</p>"
}
]
}
Cada entrada:
| Campo | Tipo | Notas |
|---|---|---|
title | string | Requerido. La etiqueta del botón. |
url | string | Ya sea una URL absoluta https://... o una ruta. Mutuamente exclusivo con html. |
html | cadena | HTML sin procesar renderizado en un modal en línea. Mutuamente exclusivo con url. |
icon | cadena | URL de imagen opcional que se muestra en el botón. Las mismas reglas de ruta que url. |
color | cadena | Color hexadecimal opcional para el fondo del botón. |
description | cadena | Opcional. Se muestra en el modal que se abre para acciones basadas en URL. |
openExternally | booleano | Si es true, la URL se abre en una nueva pestaña en lugar de un modal en línea. |
Reglas que el host aplica en el momento de carga:
- Se requiere permiso
ui.modify. Sin él, el manifiesto es rechazado. - Exactamente uno de
urlohtmlpor entrada. - Las URL relativas (y los iconos) que comienzan con
/se prefijan automáticamente al espacio de nombres de tu plugin."/"se convierte en/plugins/my-plugin/."/star.png"se convierte en/plugins/my-plugin/star.png. Te evita codificar en duro el nombre de tu plugin. - Las URL (y los iconos) que se resuelven en tu espacio de nombres requieren
http.serve, dado que tú eres el que los sirve. - Las URL (y los iconos) que apuntan al espacio de nombres de otro plugin son rechazados. Evita errores tipográficos y previene que un plugin publicite la interfaz de usuario de otro.
Cobertura completa en UI: Botones de acción.
admin: páginas de administración
Plugins can register pages that appear in the Owncast admin UI under Plugins. The pages object is keyed by plugin-relative path glob:
{
"permissions": ["http.serve"],
"admin": {
"pages": {
"/admin": { "title": "Settings", "icon": "gear" }
}
}
}
Cada entrada tiene:
| Part | Tipo | Notas |
|---|---|---|
| object key | cadena | Required path glob under the plugin's namespace, such as "/admin" or "/admin/*". |
title | cadena | Requerido. La etiqueta de la pestaña mostrada en la interfaz de administración. |
icon | cadena | Opcional. Un nombre semántico corto (gear, wrench, user, etc.). |
The host derives each page path from its object key. A key of "/admin" maps to /plugins/<your-slug>/admin. Requests matching any key are auth-gated by the host, so unauthenticated requests get a 401 before your plugin code runs.
JSON object order is not significant. Owncast displays admin pages in lexicographic path order. pages must be an object. Do not add a path member to a page value. The host rejects arrays and page values containing the legacy path member.
Cobertura completa en UI: Páginas de administración.
styles: inyección de CSS
Una lista de archivos CSS que el plugin contribuye a la página del visor. El contenido de cada archivo se inserta en el mismo bloque <style> que Owncast ya utiliza para el CSS personalizado del administrador, así que los plugins pueden personalizar la página sin que cada contribución necesite su propia etiqueta <link>.
{
"permissions": ["ui.modify"],
"styles": ["theme.css", "overrides.css"]
}
Las reglas de ruta coinciden con las URL de los botones de acción:
- Las rutas simples como
"theme.css"se prefijan automáticamente al espacio de nombres de tu plugin. - Las rutas con una sola barra como
"/theme.css"reciben el mismo tratamiento. - Las rutas completamente calificadas
/plugins/<your-slug>/...pasan sin cambios. - Las rutas en el espacio de nombres de otro plugin son rechazadas.
- Las URL
http://yhttps://son rechazadas. Agrupa activos externos (fuentes, imágenes) y refiérete a ellos con@font-faceourl(...)desde dentro de tu CSS, para que un administrador que revisa el manifiesto vea cada archivo que aterrizará en su página. - Cada entrada debe terminar en
.css.
Requiere únicamente ui.modify (el plugin se pinta dentro del marco de Owncast). No se necesita http.serve: los bytes de cada archivo se leen de assets/ y se insertan en customStyles en /api/config, no se sirven en una URL. El host emite un /* plugin: <your-slug> ... */ comentario al frente de cada contribución para que un lector pueda atribuir una regla de vuelta al plugin que la envió.
Para CSS que depende del estado del plugin, un manejador onPageStyles lo devuelve en el momento de la solicitud, sin campo de manifiesto. Su salida se agrega a customStyles después de estos archivos estáticos.
Cobertura completa en UI: Hojas de estilo del visor.
scripts: inyección de JavaScript
Una lista de archivos JavaScript que el plugin contribuye a la página del visor. El contenido de cada archivo se agrega a la misma respuesta de la que ya proviene el JavaScript personalizado del administrador (/customjavascript), de modo que los plugins pueden extender la página sin que cada contribución necesite su propia etiqueta <script>.
{
"permissions": ["ui.modify"],
"scripts": ["client.js"]
}
Las reglas de ruta y permisos requeridos coinciden con styles, aplicados a archivos .js (solo se necesita ui.modify, y el host lee de assets/ y los inserta en /customjavascript). Envuelve tu script en un IIFE para que las declaraciones a nivel superior no colisionen con el JavaScript del administrador o con otros plugins. El host emite un // plugin: <your-slug> ... comentario al frente de cada contribución y envuelve cada contribución en un bloque try/catch, de modo que un error en tiempo de ejecución de un plugin no pueda romper los demás.
Para JavaScript que depende del estado del plugin, un manejador onPageScripts lo devuelve en el momento de la solicitud, sin campo de manifiesto. Su salida se agrega a /customjavascript después de estos archivos estáticos.
Cobertura completa en UI: Scripts del visor.
extraPageContent: bloque HTML
Un objeto que contribuye un bloque HTML al área de contenido adicional del visor, precedido por el texto del administrador en /api/config.
{
"permissions": ["ui.modify"],
"extraPageContent": { "slug": "banner", "content": "content.html" }
}
| Campo | Tipo | Notas |
|---|---|---|
slug | cadena | Requerido solo cuando se omite content (el host lo pasa a onPageContent). Opcional de otro modo. Letras minúsculas, dígitos y guiones, comenzando con una letra. |
content | string | Opcional. Ruta relativa a un archivo HTML estático en assets/. Cuando está presente, los bytes de ese archivo se insertan directamente. Cuando se omite, el host llama a onPageContent en su lugar. |
Estático (con content): el host lee el archivo en el momento de la solicitud e inserta los bytes. Las mismas reglas de ruta que styles y scripts, aplicadas a una sola entrada .html. El HTML del plugin elude el procesador de markdown para que las etiquetas y atributos pasen tal como están escritos.
Dinámico (sin content): implementa onPageContent({ slug, user? }) en tu plugin para devolver HTML en el momento de la solicitud. Usa esto cuando el contenido debe variar por espectador o basarse en datos en vivo (por ejemplo, saludos personalizados o estadísticas actuales del stream). user es la identidad de chat del espectador, presente cuando está autenticado.
Requiere ui.modify. http.serve no es requerido porque el HTML se inserta en la respuesta de configuración, no se sirve como una URL. Cada contribución está envuelta con un <!-- plugin: <your-slug> ... --> comentario para que un lector pueda atribuir el marcado de vuelta.
Cobertura completa en UI: Contenido de página adicional.
tabs: pestañas de la página del espectador
The tabs object contributes tabs to the viewer page's tab row next to the built-in About and Followers tabs. Each object key is the tab's stable slug. Every value requires title, and content is optional.
{
"permissions": ["ui.modify"],
"tabs": {
"music": { "title": "Music", "content": "music.html" },
"schedule": { "title": "Schedule", "content": "schedule.html" }
}
}
Each entry has:
| Part | Notas |
|---|---|
| object key | Required stable slug. Letras minúsculas, dígitos y guiones, comenzando con una letra. The host passes this key to onTabContent when content is omitted. |
title | Requerido. La etiqueta mostrada en la pestaña. Debe ser único dentro de las pestañas del plugin. |
content | Opcional. Ruta relativa a un archivo HTML en assets/. Las mismas reglas de ruta que extraPageContent (prefijo automático a tu espacio de nombres, rutas cruzadas de plugins y URL http(s):// rechazadas, deben terminar en .html). When omitted, the host calls onTabContent. |
Within each plugin, Owncast displays tabs in lexicographic slug order. JSON object order is not significant. Ordering between tabs from different plugins is unspecified. tabs must be an object. Do not add a slug member to a tab value. The host rejects arrays and tab values containing the legacy slug member.
Requiere ui.modify. http.serve is not required: each static tab's HTML is read from assets/ and inlined into the pluginTabs[] array on /api/config. For a dynamic tab, the host passes the object key to onTabContent as slug and inlines the returned HTML.
Cobertura completa en UI: Pestañas de la página del espectador.
Contrato de manifiesto a tiempo de ejecución
Cuando tu plugin se carga, el host analiza el manifiesto y le pide al tiempo de ejecución que se registre. It compares the two and rejects the load when:
- the slugs don't match (
slugis the canonical identity on both sides) - the runtime uses a permission that wasn't declared in the manifest
version is intentionally not compared. It's informational metadata the host gates nothing on, and the SDK bakes it into the registration from the same manifest at build time anyway.
No escribes el registro tú mismo: el SDK lo genera a partir de los manejadores que defines (consulta tu referencia de SDK para ver cómo se declaran los manejadores en tu lenguaje). Saber que existe este contrato es útil al depurar. Un error "permiso solicitado en tiempo de ejecución no declarado en el manifiesto" significa que agregaste un manejador que necesita un permiso que olvidaste listar.
Ejemplo completo
Un manifiesto no trivial que utiliza la mayoría de las características:
{
"api": "1",
"name": "Stream Sidekick",
"slug": "stream-sidekick",
"version": "0.2.0",
"description": "Posts to Discord on stream start, shows an overlay, and adds a Donate button.",
"permissions": [
"chat.send",
"chat.filter",
"storage.kv",
"http.serve",
"http.sse",
"network.fetch",
"notifications.send",
"ui.modify"
],
"bot": {
"displayName": "Sidekick"
},
"network": {
"allowedHosts": ["api.discord.com", "*.example.com"]
},
"actions": [
{
"title": "Donate",
"url": "https://example.com/donate",
"openExternally": true
}
],
"admin": {
"pages": {
"/admin": { "title": "Sidekick settings", "icon": "gear" }
}
},
"styles": ["sidekick.css"],
"scripts": ["sidekick.js"],
"extraPageContent": { "slug": "intro", "content": "intro.html" },
"tabs": {
"schedule": { "title": "Schedule", "content": "schedule.html" }
}
}
Improve this page
See something missing or incorrect? Edit the English version of this page or help improve translations.
Gabe Kangas