Desarrolladores
Crear un plugin
Un plugin es una página web tuya que Tylode muestra dentro de la app, en un marco aislado. Habla con la API de Tylode a través de la app, solo con los permisos que le conceda el espacio de trabajo, así que puedes montar paneles, formularios o pequeñas herramientas que viven junto a tus páginas y tableros sin manejar tokens.
Cómo funcionan los plugins
- Es una página web. Una única página HTML servida por
httpsdesde un servidor público. Que el HTML no pase de 2 MB, para que Tylode pueda comprobar su código (mira Cambios de código y comprobación de integridad). - Se ejecuta en un iframe aislado (sandbox). El marco permite scripts y formularios y nada más. No comparte el origen de Tylode, así que no puede leer las cookies ni el almacenamiento de Tylode ni llamar a la API directamente.
- Habla con Tylode mediante
postMessage. Tylode le dice al plugin en qué espacio está, reenvía sus peticiones a la API después de comprobar sus permisos y le avisa en directo cuando algo cambia. - Actúa como la persona que lo está viendo. Las peticiones usan la sesión de quien tiene el plugin abierto, así que cada persona ve solo lo que ve en la app. No hace falta token ni plan Pro, y las peticiones cuentan para el límite de uso de esa persona.
- Se instala por espacio de trabajo. Los admins lo instalan y eligen sus permisos; todo el espacio puede usarlo.
- Cada persona lo acepta. Como trabaja con la sesión de quien lo abre, la primera vez que alguien lo abre ve sus permisos y pulsa Aceptar y abrir; si los permisos cambian, se le vuelve a preguntar.
Los plugins usan los mismos permisos (scopes) que las apps OAuth, con dos límites: Tylode pone siempre el workspace_id del espacio donde está instalado (en la ruta y en el cuerpo), y un plugin no puede usar lo personal de quien lo abre: Mi unidad (/storage), los diseños del Estudio (/presentations) ni el calendario personal (personal=true).
El protocolo postMessage
Hay cuatro tipos de mensaje. Todos son objetos normales con un campo type.
| Mensaje | Sentido | Cuándo |
|---|---|---|
tylode:init | Tylode → plugin | Cada vez que el marco termina de cargar. |
tylode:api-request | Plugin → Tylode | Cuando el plugin quiere llamar a la API. |
tylode:api-response | Tylode → plugin | La respuesta a una petición. |
tylode:event | Tylode → plugin | Algo ha cambiado en el espacio. |
tylode:init
{
type: "tylode:init",
workspaceId: "7c1e2a4b-3f5d-4e8a-9b21-6d0f5c8e1a37",
scopes: ["pages:read", "pages:write"]
}workspaceId: el espacio donde está instalado el plugin. Úsalo comoworkspace_iden tus peticiones.scopes: los permisos que concedió el espacio. Úsalos para esconder lo que el plugin no puede hacer.
Tylode lo envía cuando salta el evento load del marco, así que registra tu escucha de message en un script que se ejecute mientras carga la página, no después de una espera.
tylode:api-request
window.parent.postMessage(
{
type: "tylode:api-request",
requestId: "k2x9f7", // cualquier cadena única que elijas
method: "POST", // "GET", "POST" o "PATCH"
path: "/pages", // relativa a /api/v1, empezando por /
body: { workspace_id: "7c1e2a4b-3f5d-4e8a-9b21-6d0f5c8e1a37", title: "Notes" }
},
"*"
);| Campo | Obligatorio | Descripción |
|---|---|---|
type | Sí | "tylode:api-request". |
requestId | Sí | Una cadena que generas tú. La respuesta lleva el mismo valor, para que puedas emparejarlas cuando haya varias peticiones en marcha. |
method | Sí | GET, POST o PATCH. |
path | Sí | La ruta de la API relativa a /api/v1, con su query string: /pages?workspace_id=…, /boards/BOARD_ID/cards. |
body | No | En POST y PATCH, un objeto. Tylode lo envía como JSON. |
Antes de reenviar una petición, Tylode comprueba el permiso que necesita: GET pide el permiso :read del recurso, y POST o PATCH, su permiso :write (por ejemplo, GET /pages?… necesita pages:read y POST /boards/:id/cards necesita boards:write). Las rutas que ningún permiso cubre se rechazan siempre: /workspaces (el ID del espacio ya te llega en tylode:init) y cualquier escritura en /search. Las bases de datos usan databases:read y databases:write, como el resto. El puente no admite DELETE, así que un plugin no puede borrar aunque el espacio le conceda un permiso :delete. Tienes todos los endpoints en el índice de endpoints.
tylode:api-response
Cuando la petición ha llegado a la API:
{
type: "tylode:api-response",
requestId: "k2x9f7",
status: 201,
data: {
data: {
id: "2b9d6f1e-8a4c-4f3b-b7e2-51c0d9a8e6f4",
workspace_id: "7c1e2a4b-3f5d-4e8a-9b21-6d0f5c8e1a37",
parent_page_id: null,
title: "Notes",
icon: null,
position: 3
}
}
}statuses el código HTTP de la API.dataes el cuerpo JSON completo de la API. Así que, si todo va bien, el resultado está endata.data, y cuando la API devuelve un error, su mensaje está endata.error.
Cuando Tylode rechaza la petición porque al plugin le falta el permiso o la ruta no está disponible para plugins:
{ type: "tylode:api-response", requestId: "k2x9f7", status: 403, error: "…" }Si Tylode no consigue llegar a la API, recibes status: 500 con un error, con la misma forma.
tylode:event
Cuando algo cambia en el espacio, Tylode se lo pasa al plugin para que no tengas que preguntar cada poco:
{
type: "tylode:event",
resource: "pages",
eventType: "UPDATE",
record: { id: "2b9d6f1e-8a4c-4f3b-b7e2-51c0d9a8e6f4", title: "Notes (final)", ... }
}resource | Necesita | Salta cuando |
|---|---|---|
pages | pages:read | Se crea, cambia o borra una página. |
boards | boards:read | Se crea, cambia o borra un tablero. |
calendar_events | calendar:read | Se crea, cambia o borra un evento del calendario. |
whiteboards | whiteboards:read | Se crea, cambia o borra una pizarra. |
eventTypeesINSERT,UPDATEoDELETE.recordes el elemento tal como está guardado. Puede traer más campos de los que devuelve la API y, en los borrados, puede llegar vacío. Trata los eventos como una señal para volver a cargar a través de la API, no como datos fiables campo a campo.- Solo recibes eventos de este espacio, de los recursos cuyo permiso de lectura tiene el plugin y de los elementos que puede ver quien lo está usando.
- Los cambios dentro de esos elementos (tarjetas de un tablero, elementos de una pizarra) no generan eventos. Vuelve a cargar el tablero o la pizarra cuando llegue su propio evento o cuando lo pida el usuario.
Un ejemplo completo
Este plugin lista las páginas del espacio, permite crear una y se actualiza solo cuando cambian páginas en cualquier sitio. Está basado en el ejemplo oficial «Hello Pages» del catálogo. Instálalo con pages:read y pages:write.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Quick pages</title>
<style>
body { font-family: system-ui, sans-serif; margin: 0; padding: 16px; background: #fafafa; color: #18181b; }
h1 { font-size: 15px; margin: 0 0 12px; }
form { display: flex; gap: 6px; margin-bottom: 12px; }
input { flex: 1; padding: 6px 8px; border: 1px solid #d4d4d8; border-radius: 6px; font: inherit; }
button { padding: 6px 10px; border: 0; border-radius: 6px; background: #18181b; color: #fff; font: inherit; cursor: pointer; }
ul { list-style: none; margin: 0; padding: 0; }
li { padding: 6px 8px; border-radius: 6px; font-size: 13px; }
li:nth-child(odd) { background: #f0f0f1; }
.error { color: #dc2626; font-size: 13px; }
[hidden] { display: none !important; }
</style>
</head>
<body>
<h1>Quick pages</h1>
<form id="new-page" hidden>
<input id="title" placeholder="New page title" required />
<button type="submit">Create</button>
</form>
<p id="error" class="error" hidden></p>
<ul id="pages"></ul>
<script>
// Se rellenan cuando Tylode envía tylode:init.
let workspaceId = null;
let scopes = [];
// Peticiones que esperan respuesta, por requestId.
const pending = new Map();
// Llama a la API de Tylode a través del host. Se resuelve con el `data`
// de la API y se rechaza con el mensaje de error.
function api(method, path, body) {
const requestId = Math.random().toString(36).slice(2) + Date.now().toString(36);
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
pending.delete(requestId);
reject(new Error("Tylode didn't answer in time"));
}, 15000);
pending.set(requestId, (msg) => {
clearTimeout(timer);
if (msg.status >= 200 && msg.status < 300) {
resolve(msg.data.data); // msg.data es el cuerpo JSON de la API
} else {
reject(new Error(msg.error || (msg.data && msg.data.error) || "Error " + msg.status));
}
});
window.parent.postMessage({ type: "tylode:api-request", requestId, method, path, body }, "*");
});
}
window.addEventListener("message", (event) => {
// Acepta solo mensajes de Tylode, la página que contiene este plugin.
if (event.source !== window.parent) return;
const msg = event.data;
if (!msg || typeof msg.type !== "string") return;
if (msg.type === "tylode:init") {
workspaceId = msg.workspaceId;
scopes = msg.scopes;
// Solo se ofrece crear páginas si el espacio concedió pages:write.
document.getElementById("new-page").hidden = !scopes.includes("pages:write");
loadPages();
} else if (msg.type === "tylode:api-response") {
const settle = pending.get(msg.requestId);
if (settle) {
pending.delete(msg.requestId);
settle(msg);
}
} else if (msg.type === "tylode:event" && msg.resource === "pages") {
// Se ha creado, renombrado o borrado una página en algún sitio: recarga la lista.
loadPages();
}
});
async function loadPages() {
try {
const pages = await api("GET", `/pages?workspace_id=${workspaceId}`);
document.getElementById("pages").replaceChildren(
...pages.map((page) => {
const li = document.createElement("li");
// textContent, no innerHTML: los títulos los escriben los usuarios.
li.textContent = `${page.icon || "📄"} ${page.title || "Untitled"}`;
return li;
}),
);
showError(null);
} catch (err) {
showError(err.message);
}
}
document.getElementById("new-page").addEventListener("submit", async (event) => {
event.preventDefault();
const input = document.getElementById("title");
try {
await api("POST", "/pages", { workspace_id: workspaceId, title: input.value });
input.value = "";
loadPages(); // el tylode:event de la página nueva también provoca una recarga
} catch (err) {
showError(err.message);
}
});
function showError(message) {
const el = document.getElementById("error");
el.hidden = !message;
el.textContent = message || "";
}
</script>
</body>
</html>La parte que puedes reutilizar es el ayudante api(): da un ID a cada petición, guarda una promesa para ella y la resuelve cuando llega el tylode:api-response que le corresponde. El tiempo de espera evita que una promesa se quede esperando para siempre si el marco se recarga a mitad de una petición.
Qué permite el sandbox
El marco lleva sandbox="allow-scripts allow-forms". En la práctica:
- Funciona: tu JavaScript y tu CSS, los formularios,
fetcha tu propio servidor (verá que la petición viene del origennull, así que permítelo en su configuración de CORS), e imágenes y scripts de otros dominios. - No funciona: las cookies y el almacenamiento de Tylode, ni tampoco
localStorage,sessionStorage, IndexedDB y cookies de tu propia página; las ventanas emergentes y las ventanas nuevas;alert(),confirm()yprompt(); navegar la ventana de Tylode.
Para recordar ajustes, guárdalos en tu propio servidor o, si son del espacio, en una página de Tylode a través de la API.
Tu servidor también tiene que dejar que Tylode muestre la página dentro de un marco: no envíes X-Frame-Options: DENY ni SAMEORIGIN, y si usas una Content-Security-Policy con frame-ancestors, incluye https://app.tylode.com.
Instalar tu plugin
Tienes que ser admin del espacio para instalar plugins. El resto de miembros puede usar los que ya estén instalados.
- Abre Ajustes → Workspace → Plugins (ir allí).
- Pulsa Instalar plugin.
- Rellena Nombre y URL del plugin (se carga en un iframe aislado), que tiene que empezar por
https://. - En Permisos, marca solo lo que necesite el plugin. Cada uno aparece con su descripción, como «Leer tus páginas».
- Pulsa Instalar.
El plugin aparece como una tarjeta que indica cuántos permisos tiene. Púlsala para abrirlo a pantalla completa. Para cambiar sus permisos más adelante, desinstálalo (icono de la papelera, Desinstalar, y pulsa otra vez para confirmar) y vuelve a instalarlo.
Mientras desarrollas, sirve tu página a través de un túnel HTTPS (como ngrok o Cloudflare Tunnel) e instala la URL del túnel en un espacio de pruebas.
Cambios de código y comprobación de integridad
Al instalar un plugin, Tylode guarda una huella de su HTML. Cada vez que alguien abre el plugin, Tylode vuelve a descargar la página y compara:
- Si es igual, no se muestra nada.
- Si ha cambiado, aparece un aviso: «El código de este plugin ha cambiado desde que se instaló.». La dirección es la misma, pero el código ya no es el que se revisó al conceder los permisos. Alguien que pueda gestionar plugins lo revisa y pulsa Confiar en el código actual para guardar la huella nueva, o Ahora no para cerrarlo por el momento.
- Si Tylode no consigue descargar la página (tu servidor está caído, responde con un error o el HTML pasa de 2 MB), el aviso dice que no se ha podido comprobar el código del plugin, en lugar de avisar de un cambio.
Así que, cuando publiques una actualización, los admins de todos los espacios que tengan tu plugin instalado verán el aviso hasta que confíen en el código nuevo. Avísales antes. Solo se toma la huella del documento HTML: los scripts y estilos que se cargan desde otros archivos no cuentan. Si quieres que la comprobación cubra todo tu código, mételo dentro del HTML.
Anclarlo como panel lateral
Cualquier miembro puede anclar un plugin como panel lateral, que se queda abierto mientras se mueve por Tylode:
- Abre Ajustes → Workspace → Plugins y pasa el ratón por la tarjeta del plugin.
- Pulsa el icono de la chincheta (Anclar como panel lateral: se queda abierto mientras navegas).
Arrastra el borde del panel para cambiar su ancho, pliégalo cuando necesites sitio o desánclalo desde el propio panel o con Quitar del panel lateral en la tarjeta. El plugin anclado es personal: cada persona elige el suyo.
Si esperas que la gente ancle tu plugin, diséñalo para que funcione en una columna estrecha.
Entrar en el catálogo
El Catálogo, arriba en la página de plugins, reúne los plugins revisados por Tylode. Al elegir uno con Usar este, el formulario de instalación se rellena con los permisos que recomienda su autor, y el admin los confirma antes de instalar.
Para proponer tu plugin para el catálogo, escribe a support@tylode.com con su URL, qué hace y qué permisos necesita.
Páginas relacionadas
- Plugins en la app: instalar y usar plugins, para todo el mundo.
- Apps OAuth 2.0: los mismos permisos, para apps fuera de Tylode.
- Plataforma para desarrolladores: todos los endpoints a los que puede llamar un plugin.