Coleções
O que é uma Coleção?
Seção intitulada “O que é uma Coleção?”Uma coleção é um objeto TypeScript que descreve uma tabela de banco de dados e como ela deve aparecer no Rebase CMS. Ela define:
- Schema — Propriedades (colunas), seus tipos e regras de validação
- Relações — Chaves estrangeiras, tabelas de junção e caminhos de join
- Segurança — Políticas de Row Level Security (RLS)
- Ganchos de ciclo de vida (Lifecycle hooks) — Callbacks para operações de criação, atualização e exclusão
- Comportamento do CMS — Modos de visualização, edição inline, visualizações de entidade, ações — tudo sob
admin
Declarando uma: defineCollection
Seção intitulada “Declarando uma: defineCollection”Envolva o literal em defineCollection. Em tempo de execução, ela é a função de identidade — ela retorna o objeto inalterado —, portanto não tem custo algum. O benefício obtido é a inferência: um parâmetro de tipo const captura suas chaves de properties como tipos literais, e os campos em formato de chave do bloco admin são então verificados em relação a elas. Um nome que não seja uma de suas propriedades resulta em um erro de compilação, e não apenas em uma sugestão ausente.
import { defineCollection } from "@rebasepro/cms-types";
const products = defineCollection({ name: "Products", slug: "products", table: "products", properties: { name: { name: "Name", type: "string" }, price: { name: "Price", type: "number" } }, admin: { display: { title: "name" }, // completion: "name" | "price" sort: ["price", "asc"], // completion on the first element propertiesOrder: ["name", "price"] }}); admin: { display: { title: "nmae" } // ~~~~~~ Type '"nmae"' is not assignable to type // 'PropertyPath<…>'. Did you mean '"name"'? }Os campos verificados são display, sort, propertiesOrder e listProperties. Três formatos são aceitos além de uma chave de propriedade comum:
| Formato | Exemplo | Notas |
|---|---|---|
Caminho pontilhado em um map |
"profile.displayName" |
A raiz deve ser uma propriedade real; o caminho abaixo dela não é verificado. |
| Coluna de coleção filha | "subcollection:orders" |
Apenas propertiesOrder / listProperties. |
Uma chave de additionalFields |
"score" as AdditionalFieldKey |
Precisa da coerção de tipo (cast) — veja abaixo. |
AdditionalFieldDelegate.key é uma string simples, portanto o sistema de tipos não tem como saber quais chaves adicionais uma coleção declara. Em vez de reabrir esses campos para qualquer string, o cast torna a exceção explícita:
import type { AdditionalFieldKey } from "@rebasepro/cms-types";
propertiesOrder: ["title", "score" as AdditionalFieldKey]Importe-o de @rebasepro/cms-types em um projeto que possui um painel administrativo — essa é a versão que também faz a checagem de tipos do bloco admin. Um projeto BaaS headless, que não possui bloco admin, importa a mesma função de @rebasepro/common.
Anotar o tipo diretamente ainda funciona e ainda é verificado:
import type { PostgresCollectionConfig } from "@rebasepro/types";
const products: PostgresCollectionConfig = { name: "Products", slug: "products", table: "products", properties: { name: { name: "Name", type: "string" } }};mas uma anotação apenas valida a estrutura — ela não consegue enxergar os nomes das suas propriedades, então os campos de chave em admin passam a aceitar qualquer string como fallback. Prefira defineCollection, a menos que precise nomear o tipo explicitamente.
Anatomia: o contrato e o painel
Seção intitulada “Anatomia: o contrato e o painel”Um arquivo, dois públicos. Tudo o que importa para o banco de dados e a API fica no nível superior; tudo o que o painel administrativo renderiza fica dentro de admin.
const posts = { // ── The backend reads these ────────────────────────────── slug: "posts", table: "posts", properties: { /* … */ }, relations: [ /* … */ ], securityRules: [ /* … */ ], callbacks: { /* … */ }, history: true,
// ── The admin panel reads these ────────────────────────── admin: { icon: "FileText", listProperties: ["title", "status"], defaultViewMode: "table", entityViews: ["preview"] }};Essa separação não é cosmética. É o que permite ao Rebase funcionar como um backend independente:
- Um projeto BaaS ou headless nunca escreve um bloco
admin. Suas coleções — ou nenhuma coleção, já que o modo BaaS faz introspecção do banco de dados — descrevem dados e autorização, nada mais.@rebasepro/typesnão carrega código de interface de usuário (UI), portanto a árvore de dependências de um projeto headless permanece restrita ao servidor. - O backend nunca lê o conteúdo dentro do bloco. Ele é descartado antes que uma coleção seja serializada para o endpoint de contrato ou em um bundle de build, e é excluído da versão do schema — portanto, alterar um ícone não marca todos os SDKs gerados como desatualizados.
O bloco admin só existe se você instalar os tipos de admin
Seção intitulada “O bloco admin só existe se você instalar os tipos de admin”@rebasepro/types não declara nenhum campo admin — nem em uma coleção, nem em uma propriedade. Em um projeto BaaS, escrever um é um erro de tipo. @rebasepro/cms-types o adiciona de volta por meio de mesclagem de declarações (declaration merging), portanto uma única linha por projeto o ativa:
// config/cms.d.ts/// <reference types="@rebasepro/cms-types" />Depois disso, tipos básicos do core passam a conter um bloco totalmente tipado — um erro de digitação como icoon se torna um erro, e você obtém autocompletion:
import { defineCollection } from "@rebasepro/cms-types";
const posts = defineCollection({ slug: "posts", name: "Posts", table: "posts", properties: { title: { name: "Title", type: "string", admin: { multiline: true } } }, admin: { icon: "FileText" }});Uma extensão (augmentation) se aplica a todo o programa TypeScript, e config/ e frontend/ são programas separados — por isso a referência pertence ao pacote de configuração. Não existe um tipo wrapper AdminCollectionConfig: com o campo mesclado, CollectionConfig é o tipo usado na criação.
Migrando a partir de uma coleção plana (flat)
Seção intitulada “Migrando a partir de uma coleção plana (flat)”Antes da versão 0.11, esses campos ficavam no nível superior. Para movê-los:
node scripts/codemod/collections-admin-block.mjs config/collectionsO comando relata qualquer coisa que não possa mover com segurança — principalmente apresentação dentro de relations[].overrides, que necessita de overrides: { admin: { … } } manualmente.
import { defineCollection } from "@rebasepro/cms-types";
export const productsCollection = defineCollection({ slug: "products", // URL path and API endpoint name: "Products", // Display name (plural) singularName: "Product", // Display name (singular) table: "products", // PostgreSQL table name
properties: { name: { type: "string", name: "Product Name", validation: { required: true } }, price: { type: "number", name: "Price", validation: { required: true, min: 0 } }, category: { type: "string", name: "Category", enum: [ { id: "electronics", label: "Electronics", color: "blue" }, { id: "clothing", label: "Clothing", color: "pink" }, { id: "books", label: "Books", color: "orange" } ] }, description: { type: "string", name: "Description", admin: { multiline: true } }, active: { type: "boolean", name: "Active", defaultValue: true }, createdAt: { type: "date", name: "Created At", autoValue: "on_create", admin: { readOnly: true } } }, admin: { icon: "Package" // Lucide icon name }});Propriedades Principais
Seção intitulada “Propriedades Principais”Identificação
Seção intitulada “Identificação”| Propriedade | Tipo | Descrição |
|---|---|---|
slug |
string |
Obrigatório. Identificador seguro para URL. Usado na URL da UI administrativa e no caminho da API REST (/api/data/{slug}). |
name |
string |
Obrigatório. Nome de exibição (plural). Exibido na navegação e nos cabeçalhos de página. |
singularName |
string |
Nome de exibição para uma única entidade. Usado em “Novo Produto”, “Editar Produto”, etc. |
description |
string |
Uma frase sobre o que esta coleção armazena, exibida acima da lista. Markdown. |
table |
string |
Nome da tabela PostgreSQL. O padrão é toSnakeCase(slug) — defina-o apenas para desacoplar a URL da tabela, por exemplo, uma tabela existente blog_posts servida em /posts. |
admin.icon |
string |
Um nome de ícone do Lucide, por exemplo "FileText", "ShoppingCart". Um elemento renderizado também funciona, mas o nome sobrevive à serialização, portanto é o que o editor de esquema grava de volta. |
Esquema (Schema)
Seção intitulada “Esquema (Schema)”| Propriedade | Tipo | Descrição |
|---|---|---|
properties |
Properties |
Obrigatório. Mapeamento de chave da propriedade → definição da propriedade. Cada chave se torna uma coluna no banco de dados. |
relations |
Relation[] |
Relações SQL — chaves estrangeiras, tabelas de junção. Consulte Relations. |
securityRules |
SecurityRule[] |
Políticas de Row Level Security. Consulte Security Rules. |
indexes |
CollectionIndex[] |
Índices do Postgres que esta tabela necessita. Consulte Indexes. |
search |
SearchConfig |
Busca textual ranqueada (full-text search) nos campos especificados, incluindo conteúdo JSONB e arrays. Apenas Postgres. Consulte Search. |
auth |
boolean | AuthCollectionConfig |
Marca a coleção como de autenticação (gestão de usuários, redefinição de senha, etc.) |
schema |
string |
Esquema do Postgres onde a tabela reside — "public", "rebase", "auth". O padrão é "public". |
disableDefaultPolicies |
boolean |
Remove as políticas de base que o gerador injeta — um SELECT para admin/servidor e, em coleções de autenticação, leitura própria mais restrição de escrita exclusiva para administradores — e assume total responsabilidade pelo RLS desta coleção. Padrão: false. Consulte Security Rules. |
softDelete |
boolean | { field?: string } |
Transforma o delete em um timestamp e oculta as linhas marcadas de qualquer leitura. true utiliza deletedAt; a forma em objeto renomeia o campo. A própria coleção deve declarar essa propriedade date. Apenas Postgres — consulte Soft delete |
strictWrites |
boolean |
Rejeita uma gravação que especifique um campo não declarado por esta coleção, com erro 400. Padrão: true. Defina como false apenas quando a coluna realmente existir e não estiver declarada — preenchida por um trigger ou obtida via introspecção em vez de declarada manualmente. |
Configuração de UI
Seção intitulada “Configuração de UI”Tudo o que segue deve ficar dentro de admin.
| Propriedade | Tipo | Padrão | Descrição |
|---|---|---|---|
defaultViewMode |
"list" | "table" | "cards" | "kanban" |
"list" |
Modo de visualização padrão |
enabledViews |
ViewMode[] |
Os quatro nativos mais toda visualização customizada declarada (o kanban só quando existe uma propriedade enum) |
Quais modos de visualização estão disponíveis |
kanban |
KanbanConfig |
— | Configuração do Kanban (propriedade de coluna). Sempre combine com orderProperty — consulte View Modes |
orderProperty |
string |
— | Chave da propriedade string que armazena a ordem para drag-and-drop. Obrigatória para o funcionamento do quadro Kanban |
openEntityMode |
"side_panel" | "full_screen" | "split" | "dialog" |
A partir da visualização: "split" para a lista, "full_screen" para a tabela e os cards, "side_panel" para o kanban e as visualizações customizadas |
Como as entidades abrem para edição |
sideDialogWidth |
number | string |
— | Largura do diálogo lateral |
inlineEditing |
boolean |
true |
Habilitar edição inline na visualização em planilha/tabela |
defaultSize |
"xs" | "s" | "m" | "l" | "xl" |
"m" |
Densidade padrão das visualizações de lista, tabela e cards (a altura de linha da tabela) |
pagination |
boolean | number |
true (50) |
Habilitar paginação e/ou definir o tamanho da página. false carrega todas as linhas; em desenvolvimento o console avisa uma vez quando são mais de 10.000 linhas |
listProperties |
string[] |
— | Propriedades a serem exibidas na visualização de lista |
propertiesOrder |
string[] |
— | Ordem das colunas na visualização de tabela |
selectionEnabled |
boolean |
true |
Habilitar seleção de linhas |
hideFromNavigation |
boolean |
false |
Ocultar da barra de navegação lateral |
defaultSelectedView |
string | function |
— | Visualização padrão ou subcoleção a ser aberta |
Opções de Entidade
Seção intitulada “Opções de Entidade”Dentro de admin, exceto history, que é um recurso de backend e permanece no nível superior.
| Propriedade | Tipo | Padrão | Descrição |
|---|---|---|---|
formAutoSave |
boolean |
false |
Salva automaticamente dois segundos depois de uma edição, assim que o formulário é validado. Um formulário Novo só é salvo quando algo é preenchido. |
localChangesBackup |
"manual_apply" | "auto_apply" | false |
"manual_apply" |
Fazer backup de alterações não salvas |
hideIdFromForm |
boolean |
false |
Ocultar o ID da entidade do formulário |
hideIdFromCollection |
boolean |
false |
Ocultar a coluna de ID da tabela |
includeJsonView |
boolean |
true |
Disponibilizar os valores brutos no inspetor de registros |
history |
boolean |
false |
Rastrear alterações no histórico da entidade |
alwaysApplyDefaultValues |
boolean |
false |
Quando um registro existente é aberto no formulário, preenche o defaultValue declarado de cada propriedade em um valor que o registro deixa vazio (null ou ausente), de modo que o próximo salvamento o grave |
previewProperties |
string[] |
— | Propriedades a serem exibidas em pré-visualizações de referências |
display |
EntityDisplay |
— | O que preenche cada papel de exibição — consulte Exibição de entidade |
Avançado
Seção intitulada “Avançado”No nível superior, porque o backend os lê:
| Propriedade | Tipo | Descrição |
|---|---|---|
callbacks |
CollectionCallbacks |
Ganchos de ciclo de vida (beforeSave, afterSave, beforeDelete, etc.) |
childCollections |
() => CollectionConfig[] |
As coleções aninhadas sob uma entidade desta coleção. Preenchido durante a normalização a partir do que o driver expressa — subcollections do Firestore, relação hasMany do Postgres —, portanto, um driver customizado é o único motivo para defini-lo manualmente |
dataSource |
string |
Qual fonte de dados registrada dá suporte a esta coleção (padrão: a não nomeada) |
engine |
string |
A engine por trás dela — "postgres", "firestore", "mongodb". Resolvido a partir de dataSource; defina apenas para sobrescrever |
databaseId |
string |
Banco de dados ou esquema dentro da engine |
metadata |
Record<string, unknown> |
Qualquer informação que seu próprio código precise associar a uma coleção. O Rebase não lê este campo; ele sobrevive à serialização inalterado |
ownerId |
string |
Apenas formulário administrativo — não imposto pela API ou pelo banco de dados. O ID de usuário que o editor de coleções vincula a uma coleção criada por ele e exibe ao lado de seu nome. Nada no caminho da requisição o consulta |
subcollections e path estão presentes apenas nas configurações de bancos de dados de documentos — FirebaseCollectionConfig e, para path, MongoDBCollectionConfig:
| Propriedade | Tipo | Descrição |
|---|---|---|
subcollections |
() => CollectionConfig[] |
Apenas Firestore. Coleções aninhadas sob cada documento. Uma coleção Postgres expressa o mesmo por meio de uma relação hasMany, que é o que preenche childCollections |
path |
string |
Apenas Firestore e MongoDB. O caminho ou nome da coleção na engine, caso seja diferente do slug |
E dentro de admin, porque apenas o painel os renderiza:
| Propriedade | Tipo | Descrição |
|---|---|---|
admin.entityActions |
EntityAction[] |
Ações personalizadas em entidades (arquivar, publicar, etc.) |
admin.Actions |
React.ComponentType |
Componente personalizado de ações na barra de ferramentas |
admin.entityViews |
EntityCustomView[] |
Abas personalizadas na visualização de detalhes da entidade |
admin.additionalFields |
AdditionalFieldDelegate[] |
Colunas virtuais/computadas |
admin.exportable |
boolean | ExportConfig |
Habilitar exportação de dados |
admin.components |
CollectionComponentOverrideMap |
Sobrescrita de componentes de interface no escopo da coleção |
Escrever qualquer um desses seis no nível superior resulta em um erro durante a inicialização (boot-time), com uma mensagem indicando a chave e para onde ela foi movida.
Exibição de entidade
Seção intitulada “Exibição de entidade”Cada superfície que renderiza um registro exibe algum subconjunto de seis papéis: title, subtitle, image, status, date e tags. Uma linha de lista é image + title + subtitle + status + date; um card é o mesmo com a imagem no topo; um seletor de referência é title + subtitle; e o cabeçalho de uma página é apenas o title.
Cada papel é derivado das suas propriedades, e cada um pode ser declarado explicitamente — como um caminho de propriedade (property path) ou como uma função:
const exercises = defineCollection({ name: "Exercises", slug: "exercises", table: "exercises", properties: { name: { name: "Name", type: "string" }, cover: { name: "Cover", type: "string", storage: { storagePath: "covers/" } }, city: { name: "City", type: "string" } }, admin: { display: { title: "name", // a property path image: "cover", subtitle: ({ entity }) => `in ${entity.values.city}` // computed } }});Qualquer papel que você omitir mantém seu valor derivado, portanto declarar um papel não significa ter que declarar os seis.
Papéis computados e assíncronos
Seção intitulada “Papéis computados e assíncronos”Um resolvedor (resolver) pode ser async, o que permite a um papel ler algo que o registro não contém — um documento em uma subcoleção, um valor retornado por uma API:
admin: { display: { // The exercise's name lives one document down, per locale. title: async ({ entity, context }) => { const locale = await context.data.exercise_locales.get(`${entity.id}/de-DE`); return locale?.exercise_title; } }}Enquanto a promise está pendente, a superfície exibe o valor derivado e o substitui pelo valor resolvido assim que este estiver disponível — um título nunca é exibido como um spinner. Os resultados são mantidos em cache por registro e por papel, e requisições concorrentes para o mesmo par compartilham uma única chamada, de modo que uma lista de cinquenta linhas resolve cada linha apenas uma vez, em vez de uma vez por renderização.
Retorne undefined quando um registro não tiver nada para o papel; o fallback da própria superfície está mais bem preparado para decidir o que colocar no lugar (um cabeçalho usa o nome singular da coleção, um link usa o ID). Um resolver que lança uma exceção é tratado como undefined e registrado em log uma única vez — um título que falhar ao ser buscado não deve quebrar a linha que o exibe.
Prefira um caminho sempre que o valor já estiver no registro: um caminho preserva a renderização nativa da propriedade, de forma que um status de enum continue sendo uma tag colorida (chip) e uma data permaneça formatada, o que um resolver que retorna uma string simples não consegue expressar.
Seleção da Propriedade de Título
Seção intitulada “Seleção da Propriedade de Título”Quando display.title não está definido, a propriedade usada como o título de exibição da entidade (pré-visualizações, cabeçalhos) é resolvida automaticamente:
- Se
propertiesOrderestiver explicitamente definido, a primeira propriedade que não seja de ID e que seja do tiporelationoustringé escolhida como o título. - Se nenhum
propertiesOrderestiver definido, o framework busca as propriedades em ordem e escolhe a primeira propriedade do tipo string.
Pré-visualizações de Relações em Tabelas
Seção intitulada “Pré-visualizações de Relações em Tabelas”Quando propertiesOrder é definido explicitamente, as propriedades de relação não são filtradas automaticamente das colunas de pré-visualização padrão (ao passo que são excluídas dos padrões não ordenados para evitar operações lentas de join).
Como o valor de um título é renderizado
Seção intitulada “Como o valor de um título é renderizado”Independentemente do que a propriedade de título contenha, o painel renderiza uma string. Uma data é formatada, um array é concatenado, e uma relação — que chega como { id, data: { values } } em vez de texto — é examinada em busca do primeiro valor entre name, title, label ou displayName na linha relacionada, usando o seu id como fallback. Assim, um título pode apontar para uma propriedade relation e ainda assim ser exibido como um nome, e não como um UUID.
Isso não é um helper exportado: é o que cada superfície que desenha um registro já faz. Não há nada a chamar nem nada a importar.
Construtor de Coleção (Collection Builder)
Seção intitulada “Construtor de Coleção (Collection Builder)”Para coleções dinâmicas que mudam com base no usuário ou em dados externos, use uma função construtora (builder):
const collectionsBuilder: CollectionConfigsBuilder = ({ user, authController }) => { const collections = [productsCollection];
// `extra` is whatever your auth provider put there, so name its shape here. const extra = authController.extra as { role?: string }; if (extra.role === "admin") { collections.push(adminSettingsCollection); }
return collections;};Filtragem e Ordenação
Seção intitulada “Filtragem e Ordenação”Você pode definir filtros padrão ou forçados. Todos os três são aspectos de apresentação — como o painel é inicializado —, portanto residem em admin:
import { defineCollection } from "@rebasepro/cms-types";
const invoices = defineCollection({ slug: "invoices", name: "Invoices", table: "invoices", properties: { active: { name: "Active", type: "boolean" }, tenantId: { name: "Tenant", type: "string" }, createdAt: { name: "Created", type: "date" } }, admin: { // Default filter — users can change it defaultFilter: { active: ["==", true] },
// Fixed filter — cannot be changed; filters the user applies // on other fields combine with it fixedFilter: { tenantId: ["==", currentTenantId] },
// Default sort sort: ["createdAt", "desc"] }});O usuário ainda pode filtrar uma coleção que tem um fixedFilter: o que ele
aplica em outros campos é combinado com ele (AND), e os campos fixos permanecem
bloqueados.
Um fixedFilter restringe o que o painel solicita; ele não é um limite de segurança. O que um solicitante tem permissão para ler é uma regra de segurança, que o banco de dados impõe para qualquer chamador, seja ele o painel ou não.
Muitas coleções
Seção intitulada “Muitas coleções”Nada no Rebase limita o número de coleções, mas dois custos crescem com ele:
- O primeiro download do admin. O painel precisa da sua navegação no momento em que alguém entra, então a configuração de cada coleção é incluída no que o navegador baixa para a tela de login — cerca de 2,4 KB minificados cada, de modo que 200 coleções acrescentam aproximadamente 480 KB, em torno de 80 KB com gzip. O que abrir uma coleção exige (a tabela, as visualizações de registro, o formulário) só é carregado nesse momento.
rebase db push. O Atlas planeja um push comparando o schema inteiro, e isso cresce mais rápido que o número de tabelas: com 400 ou mais tabelas um push leva dezenas de segundos — veja quanto tempo leva um push.
Próximos Passos
Seção intitulada “Próximos Passos”- Entity Callbacks — Ganchos de ciclo de vida para sincronizar dados entre coleções, validação e efeitos colaterais
- Properties — Todos os tipos de propriedade e opções
- Relations — Chaves estrangeiras, tabelas de junção, joins
- Security Rules — Políticas de Row Level Security
- View Modes — Lista, Tabela, Cards, Kanban