Sistema de plugins
Descripción general
Sección titulada «Descripción general»Los plugins son un concepto del panel de administración. Se ejecutan en el navegador, dentro de la interfaz de administración de React, y se registran donde la construyes. Nada en esta página llega al backend: un plugin no puede añadir una ruta, un callback o un cron. Para eso, consulta Funciones personalizadas, Callbacks de entidad y Tareas cron.
Los plugins son el mecanismo principal de extensión en el panel. Pueden:
- Envolver toda la aplicación con un provider (contexto, gestión de estado)
- Añadir acciones de la página de inicio y widgets
- Inyectar componentes de vista de colección (barra de herramientas, constructores de columnas)
- Añadir componentes de formulario (constructores de campos, paneles adicionales)
- Inyectar o modificar colecciones dinámicamente
Interfaz del plugin
Sección titulada «Interfaz del plugin»interface RebasePlugin { key: string; // Unique identifier loading?: boolean; // Hold admin content until the plugin is ready
// UI contributions — a flat array, each entry naming its slot. // This replaced the old per-area objects (homePage, collectionView, form). slots?: SlotContribution[];
// HOC providers. `scope: "root"` wraps the whole admin below // RebaseContext; `scope: "form"` wraps each entity form / edit view. providers?: PluginProvider[];
// Behavioural (non-UI) hooks: collection modification and injection, // column reordering, navigation entries. hooks?: PluginHooks;
// Custom field rendering (e.g. data enhancement). fieldBuilder?: FieldBuilderConfig;
// Views added to the navigation automatically. views?: AppView[];
// onMount, onAuthStateChange, onUnmount — see below. lifecycle?: PluginLifecycle;}Cada uno de estos es opcional excepto key. La lista completa de nombres de slots
se encuentra en la página de Slots.
Ciclo de vida
Sección titulada «Ciclo de vida»onMount(context)se ejecuta una vez, cuando la autenticación está lista por primera vez: hay un usuario con sesión iniciada, o se omitió el inicio de sesión.onAuthStateChange(user)se ejecuta en cada cambio de usuario posterior: con el nuevo usuario al iniciar sesión, connullal cerrarla. El plugin sigue montado en ambos casos.onUnmount()se ejecuta cuando<Rebase>se desmonta.
Desde 0.25
lifecycle.onCollectionChange está deprecado. Nada lo llamó nunca, ni en 0.24
ni antes. Un plugin que lo define sigue compilando, y <Rebase> avisa una vez por
plugin. Para seguir las filas de una colección, suscríbete desde onMount y
cancela la suscripción en onUnmount:
import type { RebasePlugin } from "@rebasepro/cms-types";
let stopListening: (() => void) | undefined;
const openOrdersPlugin: RebasePlugin = { key: "open_orders", lifecycle: { onMount: (context) => { stopListening = context.data.collection("orders").listen( { where: { status: ["==", "open"] } }, ({ data }) => console.log(`${data.length} open orders`) ); }, onUnmount: () => stopListening?.() }};Uso de plugins
Sección titulada «Uso de plugins»Los plugins se colocan en <Rebase>, junto al cliente. Todo lo que esté
debajo —la navegación, las vistas de colección, los formularios— los lee desde allí:
import { Rebase, useRebaseAuthController } from "@rebasepro/app";import { RebaseCMS, RebaseShell } from "@rebasepro/cms";import { useDataEnhancementPlugin } from "@rebasepro/plugin-ai";
export function App() { const authController = useRebaseAuthController({ client }); const dataEnhancementPlugin = useDataEnhancementPlugin();
return ( <Rebase client={client} authController={authController} plugins={[dataEnhancementPlugin]} > <RebaseCMS collections={collections}/> <RebaseShell title="My App"/> </Rebase> );}Por lo general, los plugins se construyen mediante un hook, por lo que el array
se reconstruye en cada renderizado; esto es normal y es la razón por la cual
plugins es una prop en lugar de algo que debas memorizar manualmente. Dos
plugins con la misma key son un error: <Rebase> registra los duplicados en
lugar de descartar uno silenciosamente.
Para una sola contribución no necesitas un plugin en absoluto: <Rebase slots>
acepta directamente las mismas entradas SlotContribution.
Con composición manual
Sección titulada «Con composición manual»Solo si has reemplazado <RebaseShell> con las capas subyacentes, la lista de
plugins debe pasarse manualmente al controlador de navegación:
const navigationStateController = useBuildNavigationStateController({ plugins, collections: () => collections, // These four are required — the controller resolves navigation against them. authController, data, collectionRegistryController, urlController});<RebaseNavigation> realiza exactamente esta llamada por ti, leyendo plugins
del controlador de personalización que proporciona <Rebase>. Consulta
Avanzado: diseño manual.
Creación de un plugin
Sección titulada «Creación de un plugin»Aquí tienes un plugin mínimo que añade una acción a la barra de herramientas en cada colección:
import type { RebasePlugin } from "@rebasepro/cms-types";
function useMyPlugin(): RebasePlugin { return { key: "my_plugin",
// `slots` is a flat array of contributions, each naming its slot. // See the Slots page for the full list of slot names. slots: [ { slot: "collection.actions", Component: MyToolbarAction } ],
// `fieldBuilder` is top-level and takes a `wrap` function that returns // a *component* (or null to leave the field as it is) — it is not // a render function and no longer lives under `form`. fieldBuilder: { wrap: ({ property }) => property.propertyConfig === "my_custom_field" ? MyCustomField : null } };}Envoltorios de campo de varios plugins
Sección titulada «Envoltorios de campo de varios plugins»Desde 0.25 En 0.24 el wrap de cada
plugin recibía el campo integrado, y solo se renderizaba el último envoltorio
devuelto.
Los envoltorios de varios plugins se componen, en el orden del array plugins.
El wrap de cada plugin recibe, como params.Field, el campo tal como lo dejaron los plugins
anteriores, así que el envoltorio del primer plugin es el más interno y el del último
el más externo. Un plugin cuyo wrap devuelve null, o cuya guarda enabled
devuelve false para el campo, lo pasa sin cambios. Un envoltorio que
renderiza Field conserva, por tanto, todos los envoltorios anteriores:
import type { RebasePlugin } from "@rebasepro/cms-types";import { Typography } from "@rebasepro/ui";
const descriptionPlugin: RebasePlugin = { key: "description_below", fieldBuilder: { // `Field` is the built-in field, or the earlier plugins' wrapper of it. wrap: ({ Field, property }) => property.description ? (props) => ( <> <Field {...props}/> <Typography variant="caption" color="secondary"> {property.description} </Typography> </> ) : null }};Plugins integrados
Sección titulada «Plugins integrados»Plugin Data Enhancement
Sección titulada «Plugin Data Enhancement»Autocompletado de campos impulsado por IA:
import { useDataEnhancementPlugin } from "@rebasepro/plugin-ai";
const enhancementPlugin = useDataEnhancementPlugin();Añade una acción Autofill a cada formulario y un comando de barra Autocomplete
a cada campo markdown, que continúa el texto desde el cursor. Ambos aparecen solo
donde el plugin está habilitado: el servicio se declara disponible y
getConfigForPath (cuando le pasas uno) permite la colección. Un campo cuyo
customProps.editorProps.aiController nombra un controlador propio usa ese.
Desde 0.25 Otro plugin puede dar a los campos markdown un controlador de la misma forma, con
EditorAIControllerProvider de @rebasepro/cms en un proveedor con alcance de formulario.

Inyección de colecciones
Sección titulada «Inyección de colecciones»Los plugins pueden añadir nuevas colecciones dinámicamente:
hooks: { // Receives the resolved collections and returns the full list to use. injectCollections: (collections) => [...collections, auditLogCollection]}Modificación de colecciones
Sección titulada «Modificación de colecciones»Los plugins pueden modificar colecciones existentes:
hooks: { // Receives one collection, returns the modified one. modifyCollection: (collection) => ({ ...collection, properties: { ...collection.properties, last_modified_by: { type: "string", name: "Modified By", admin: { readOnly: true } } } })}modifyCollection es síncrono, y el panel de administración lo aplica cuando resuelve
sus colecciones, no cada vez que cambia el plugin. Cuando el cambio necesita datos
que hay que obtener, obtenlos antes de renderizar <Rebase> y construye el plugin
a partir de lo que llegó:
import { useEffect, useMemo, useState } from "react";import { Rebase, useRebaseAuthController } from "@rebasepro/app";import { RebaseCMS, RebaseShell } from "@rebasepro/cms";import type { RebasePlugin } from "@rebasepro/cms-types";import { CircularProgressCenter } from "@rebasepro/ui";
export function App() { const authController = useRebaseAuthController({ client }); const [labels, setLabels] = useState<Record<string, string>>();
useEffect(() => { fetch("/collection-labels.json") .then((response) => response.json()) .then(setLabels); }, []);
const labelsPlugin = useMemo<RebasePlugin | undefined>(() => labels && { key: "collection_labels", hooks: { modifyCollection: (collection) => ({ ...collection, name: labels[collection.slug] ?? collection.name }) } }, [labels]);
if (!labelsPlugin) return <CircularProgressCenter/>;
return ( <Rebase client={client} authController={authController} plugins={[labelsPlugin]}> <RebaseCMS collections={collections}/> <RebaseShell title="My App"/> </Rebase> );}Desde 0.25
hooks.modifyCollectionAsync está deprecado. Nada lo llamó nunca, ni en 0.24
ni antes. Un plugin que lo define sigue compilando, y <Rebase> avisa una vez por
plugin. Usa modifyCollection como se muestra arriba.
Próximos pasos
Sección titulada «Próximos pasos»- Herramientas de Studio — Consola SQL, consola JS, editor RLS
- Campos personalizados — Creación de campos de formulario personalizados