Sistema de Plugins
Visão Geral
Seção intitulada “Visão Geral”Plugins são um conceito do painel de administração. Eles são executados no navegador, dentro do admin React, e são registrados onde você o constrói. Nada nesta página alcança o backend: um plugin não pode adicionar uma rota, um callback ou um cron. Para isso, consulte Funções Personalizadas, Callbacks de Entidade e Tarefas Cron.
Os plugins são o principal mecanismo de extensão no painel. Eles podem:
- Envolver toda a aplicação com um provider (contexto, gerenciamento de estado)
- Adicionar ações na página inicial e widgets
- Injetar componentes de visualização de coleção (barra de ferramentas, construtores de coluna)
- Adicionar componentes de formulário (construtores de campo, painéis adicionais)
- Injetar ou modificar coleções dinamicamente
Interface do Plugin
Seção intitulada “Interface do 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 um desses itens é opcional, exceto key. A lista completa de nomes de slots
está na página de Slots.
Ciclo de vida
Seção intitulada “Ciclo de vida”onMount(context)é executado uma vez, quando a autenticação fica pronta pela primeira vez: um usuário está logado, ou o login foi pulado.onAuthStateChange(user)é executado a cada mudança de usuário posterior: com o novo usuário ao fazer login, comnullao sair. O plugin continua montado nos dois casos.onUnmount()é executado quando<Rebase>é desmontado.
Desde 0.25
lifecycle.onCollectionChange está obsoleto. Nada nunca o chamou, na 0.24
ou antes. Um plugin que o define continua compilando, e o <Rebase> avisa uma vez por
plugin. Para acompanhar as linhas de uma coleção, assine a partir de onMount e
cancele a assinatura em 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?.() }};Usando Plugins
Seção intitulada “Usando Plugins”Os plugins são adicionados no <Rebase>, junto ao client. Tudo abaixo dele — a
navegação, as visualizações de coleção, os formulários — os lê a partir dali:
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> );}Os plugins geralmente são construídos por um hook, portanto, o array é reconstruído
a cada renderização; isso é esperado e é a razão pela qual plugins é uma prop
em vez de algo memorizado manualmente com memoize. Dois plugins com a mesma key
são um erro — o <Rebase> registra os duplicados no log em vez de descartar um
silenciosamente.
Para uma única contribuição, você não precisa de um plugin: <Rebase slots>
aceita diretamente as mesmas entradas de SlotContribution.
Sob composição manual
Seção intitulada “Sob composição manual”Apenas se você tiver substituído o <RebaseShell> pelas camadas subjacentes a
ele, a lista de plugins precisará ser repassada manualmente para o controlador de
navegação:
const navigationStateController = useBuildNavigationStateController({ plugins, collections: () => collections, // These four are required — the controller resolves navigation against them. authController, data, collectionRegistryController, urlController});O <RebaseNavigation> faz exatamente essa chamada para você, lendo plugins
a partir do controlador de customização fornecido pelo <Rebase>. Consulte
Avançado: layout manual.
Construindo um Plugin
Seção intitulada “Construindo um Plugin”Aqui está um plugin mínimo que adiciona uma ação de barra de ferramentas a cada coleção:
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 } };}Wrappers de campo de vários plugins
Seção intitulada “Wrappers de campo de vários plugins”Desde 0.25 Na 0.24 o wrap de cada
plugin recebia o campo integrado, e apenas o último wrapper
retornado era renderizado.
Os wrappers de vários plugins se compõem, na ordem do array plugins.
O wrap de cada plugin recebe, como params.Field, o campo como os plugins
anteriores a ele o deixaram, de modo que o wrapper do primeiro plugin fica por dentro e o do último
por fora. Um plugin cujo wrap retorna null, ou cuja proteção enabled
retorna false para o campo, o repassa inalterado. Um wrapper que
renderiza Field mantém, portanto, todos os wrappers anteriores a ele:
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 Nativos
Seção intitulada “Plugins Nativos”Plugin de Aprimoramento de Dados
Seção intitulada “Plugin de Aprimoramento de Dados”Preenchimento automático de campos com tecnologia de IA:
import { useDataEnhancementPlugin } from "@rebasepro/plugin-ai";
const enhancementPlugin = useDataEnhancementPlugin();Ele adiciona uma ação Autofill a todo formulário e um comando de barra Autocomplete
a todo campo markdown, que continua o texto a partir do cursor. Ambos aparecem
apenas onde o plugin está habilitado: o serviço se declara disponível e
getConfigForPath (quando você passa um) permite a coleção. Um campo cujo
customProps.editorProps.aiController nomeia um controlador próprio usa
esse. Desde 0.25 Outro plugin pode dar aos campos markdown um controlador da mesma forma, com
EditorAIControllerProvider de @rebasepro/cms em um provider no escopo do formulário.

Injeção de Coleções
Seção intitulada “Injeção de Coleções”Plugins podem adicionar novas coleções dinamicamente:
hooks: { // Receives the resolved collections and returns the full list to use. injectCollections: (collections) => [...collections, auditLogCollection]}Modificação de Coleções
Seção intitulada “Modificação de Coleções”Plugins podem modificar coleções 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 } } } })}O modifyCollection é síncrono, e a administração o aplica quando resolve
suas coleções, não toda vez que o plugin muda. Quando a mudança precisa de dados
que você tem de buscar, busque-os antes de renderizar o <Rebase> e construa o plugin
a partir do que voltou:
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á obsoleto. Nada nunca o chamou, na 0.24
ou antes. Um plugin que o define continua compilando, e o <Rebase> avisa uma vez por
plugin. Use modifyCollection, como acima.
Próximos Passos
Seção intitulada “Próximos Passos”- Ferramentas do Studio — Console SQL, console JS, editor RLS
- Campos Personalizados — Construção de campos de formulário personalizados